From b7e032b3c0b63555d2a9756ab49d6ac7408c701c Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 6 Nov 2018 11:36:31 +0100 Subject: [PATCH 001/123] Use HTTPlug 2.0 (#114) * Adjust client signatures * Lock lower bounds for test dependencies * Use leanphp/phpspec-code-coverage as a maintained fork --- .travis.yml | 18 +++++++----------- composer.json | 12 ++++++++---- phpspec.ci.yml | 2 +- .../HttpClientPool/LeastUsedClientPoolSpec.php | 4 ---- src/BatchClient.php | 3 ++- src/HttpAsyncClientEmulator.php | 3 ++- src/HttpClientDecorator.php | 3 ++- src/HttpClientEmulator.php | 3 ++- src/HttpClientPool.php | 3 ++- src/HttpClientPoolItem.php | 3 ++- src/HttpClientRouter.php | 3 ++- src/HttpMethodsClient.php | 2 +- src/PluginClient.php | 3 ++- 13 files changed, 33 insertions(+), 29 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6af5f34..0c9a55f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,6 @@ language: php - sudo: false -dist: trusty - cache: directories: - $HOME/.composer/cache/files @@ -19,17 +16,15 @@ branches: matrix: fast_finish: true include: - - php: 7.1 - env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" DEPENDENCIES="doctrine/instantiator:^1.1" + - php: 7.0 + env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" # Test the latest stable release - - php: 5.4 - - php: 5.5 - - php: 5.6 - php: 7.0 - php: 7.1 - php: 7.2 - env: COVERAGE=true TEST_COMMAND="composer test-ci" DEPENDENCIES="henrikbjorn/phpspec-code-coverage:^1.0" + - php: 7.2 + env: COVERAGE=true TEST_COMMAND="composer test-ci" DEPENDENCIES="leanphp/phpspec-code-coverage" # Test LTS versions - php: 7.1 @@ -44,9 +39,10 @@ matrix: env: STABILITY="dev" allow_failures: - # Latest dev is allowed to fail. + - php: 7.3 + sudo: required - env: STABILITY="dev" - + before_install: - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi - if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi; diff --git a/composer.json b/composer.json index 4695c4c..fc3d1c1 100644 --- a/composer.json +++ b/composer.json @@ -11,15 +11,18 @@ } ], "require": { - "php": "^5.4 || ^7.0", - "php-http/httplug": "^1.1", + "php": "^7.0", + "php-http/httplug": "^2.0", "php-http/message-factory": "^1.0", "php-http/message": "^1.6", "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^2.5 || ^3.4 || ^4.2", - "guzzlehttp/psr7": "^1.4" + "doctrine/instantiator": ">=1.0.5", + "guzzlehttp/psr7": "^1.4", + "phpspec/phpspec": "^3.4 || ^4.2", + "phpspec/prophecy": ">=1.8", + "sebastian/comparator": ">=2" }, "suggest": { "php-http/logger-plugin": "PSR-3 Logger plugin", @@ -37,6 +40,7 @@ }, "extra": { "branch-alias": { + "dev-2.x": "2.x-dev", "dev-master": "1.8-dev" } } diff --git a/phpspec.ci.yml b/phpspec.ci.yml index 0838ef5..d8e1383 100644 --- a/phpspec.ci.yml +++ b/phpspec.ci.yml @@ -4,7 +4,7 @@ suites: psr4_prefix: Http\Client\Common formatter.name: pretty extensions: - - PhpSpec\Extension\CodeCoverageExtension + LeanPHP\PhpSpec\CodeCoverage\CodeCoverageExtension: ~ code_coverage: format: clover output: build/coverage.xml diff --git a/spec/HttpClientPool/LeastUsedClientPoolSpec.php b/spec/HttpClientPool/LeastUsedClientPoolSpec.php index a976c31..82855d9 100644 --- a/spec/HttpClientPool/LeastUsedClientPoolSpec.php +++ b/spec/HttpClientPool/LeastUsedClientPoolSpec.php @@ -72,10 +72,6 @@ public function it_reenable_client(HttpClient $client, RequestInterface $request public function it_uses_the_lowest_request_client(HttpClientPoolItem $client1, HttpClientPoolItem $client2, RequestInterface $request, ResponseInterface $response) { - if (extension_loaded('xdebug')) { - throw new SkippingException('This test fail when xdebug is enable on PHP < 7'); - } - $this->addHttpClient($client1); $this->addHttpClient($client2); diff --git a/src/BatchClient.php b/src/BatchClient.php index 2036355..2a39904 100644 --- a/src/BatchClient.php +++ b/src/BatchClient.php @@ -6,6 +6,7 @@ use Http\Client\HttpClient; use Http\Client\Common\Exception\BatchException; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; /** * BatchClient allow to sends multiple request and retrieve a Batch Result. @@ -32,7 +33,7 @@ public function __construct(HttpClient $client) /** * {@inheritdoc} */ - public function sendRequest(RequestInterface $request) + public function sendRequest(RequestInterface $request): ResponseInterface { return $this->client->sendRequest($request); } diff --git a/src/HttpAsyncClientEmulator.php b/src/HttpAsyncClientEmulator.php index c0ba354..ce16f84 100644 --- a/src/HttpAsyncClientEmulator.php +++ b/src/HttpAsyncClientEmulator.php @@ -5,6 +5,7 @@ use Http\Client\Exception; use Http\Client\Promise; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; /** * Emulates an HTTP Async Client in an HTTP Client. @@ -18,7 +19,7 @@ trait HttpAsyncClientEmulator * * @see HttpClient::sendRequest */ - abstract public function sendRequest(RequestInterface $request); + abstract public function sendRequest(RequestInterface $request): ResponseInterface; /** * {@inheritdoc} diff --git a/src/HttpClientDecorator.php b/src/HttpClientDecorator.php index a33d5ef..da5a1bb 100644 --- a/src/HttpClientDecorator.php +++ b/src/HttpClientDecorator.php @@ -4,6 +4,7 @@ use Http\Client\HttpClient; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; /** * Decorates an HTTP Client. @@ -22,7 +23,7 @@ trait HttpClientDecorator * * @see HttpClient::sendRequest */ - public function sendRequest(RequestInterface $request) + public function sendRequest(RequestInterface $request): ResponseInterface { return $this->httpClient->sendRequest($request); } diff --git a/src/HttpClientEmulator.php b/src/HttpClientEmulator.php index dbec1ab..8e6472a 100644 --- a/src/HttpClientEmulator.php +++ b/src/HttpClientEmulator.php @@ -3,6 +3,7 @@ namespace Http\Client\Common; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; /** * Emulates an HTTP Client in an HTTP Async Client. @@ -16,7 +17,7 @@ trait HttpClientEmulator * * @see HttpClient::sendRequest */ - public function sendRequest(RequestInterface $request) + public function sendRequest(RequestInterface $request): ResponseInterface { $promise = $this->sendAsyncRequest($request); diff --git a/src/HttpClientPool.php b/src/HttpClientPool.php index 7ac292c..6f4597f 100644 --- a/src/HttpClientPool.php +++ b/src/HttpClientPool.php @@ -6,6 +6,7 @@ use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; /** * A http client pool allows to send requests on a pool of different http client using a specific strategy (least used, @@ -52,7 +53,7 @@ public function sendAsyncRequest(RequestInterface $request) /** * {@inheritdoc} */ - public function sendRequest(RequestInterface $request) + public function sendRequest(RequestInterface $request): ResponseInterface { return $this->chooseHttpClient()->sendRequest($request); } diff --git a/src/HttpClientPoolItem.php b/src/HttpClientPoolItem.php index 09cd6dd..a46ee2f 100644 --- a/src/HttpClientPoolItem.php +++ b/src/HttpClientPoolItem.php @@ -6,6 +6,7 @@ use Http\Client\HttpClient; use Psr\Http\Message\RequestInterface; use Http\Client\Exception; +use Psr\Http\Message\ResponseInterface; /** * A HttpClientPoolItem represent a HttpClient inside a Pool. @@ -50,7 +51,7 @@ public function __construct($client, $reenableAfter = null) /** * {@inheritdoc} */ - public function sendRequest(RequestInterface $request) + public function sendRequest(RequestInterface $request): ResponseInterface { if ($this->isDisabled()) { throw new Exception\RequestException('Cannot send the request as this client has been disabled', $request); diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index 9f72133..fa32c56 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -7,6 +7,7 @@ use Http\Client\HttpClient; use Http\Message\RequestMatcher; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; /** * Route a request to a specific client in the stack based using a RequestMatcher. @@ -23,7 +24,7 @@ final class HttpClientRouter implements HttpClient, HttpAsyncClient /** * {@inheritdoc} */ - public function sendRequest(RequestInterface $request) + public function sendRequest(RequestInterface $request): ResponseInterface { $client = $this->chooseHttpClient($request); diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index 58804fc..0ec1e14 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -198,7 +198,7 @@ public function send($method, $uri, array $headers = [], $body = null) * * {@inheritdoc} */ - public function sendRequest(RequestInterface $request) + public function sendRequest(RequestInterface $request): ResponseInterface { return $this->httpClient->sendRequest($request); } diff --git a/src/PluginClient.php b/src/PluginClient.php index 93aea8f..7413152 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -9,6 +9,7 @@ use Http\Client\Promise\HttpFulfilledPromise; use Http\Client\Promise\HttpRejectedPromise; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; use Symfony\Component\OptionsResolver\OptionsResolver; /** @@ -67,7 +68,7 @@ public function __construct($client, array $plugins = [], array $options = []) /** * {@inheritdoc} */ - public function sendRequest(RequestInterface $request) + public function sendRequest(RequestInterface $request): ResponseInterface { // If we don't have an http client, use the async call if (!($this->client instanceof HttpClient)) { From 1adbf741d75c20262b5e6723b3601a57bbd6b164 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 19 Nov 2018 11:51:04 +0100 Subject: [PATCH 002/123] [2.0] Remove deprecations (#116) Remove deprecated plugin option 'debug_plugins' --- CHANGELOG.md | 5 +++++ src/PluginClient.php | 18 +----------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a32a18d..6c268e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 2.0 (unreleased) + +### Removed +- Deprecated option `debug_plugins` has been removed from `PluginClient` + ## 1.8.2 (unreleased) ### Changed diff --git a/src/PluginClient.php b/src/PluginClient.php index 7413152..70419e3 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -109,28 +109,12 @@ public function sendAsyncRequest(RequestInterface $request) */ private function configure(array $options = []) { - if (isset($options['debug_plugins'])) { - @trigger_error('The "debug_plugins" option is deprecated since 1.5 and will be removed in 2.0.', E_USER_DEPRECATED); - } - $resolver = new OptionsResolver(); $resolver->setDefaults([ 'max_restarts' => 10, - 'debug_plugins' => [], ]); - $resolver - ->setAllowedTypes('debug_plugins', 'array') - ->setAllowedValues('debug_plugins', function (array $plugins) { - foreach ($plugins as $plugin) { - // Make sure each object passed with the `debug_plugins` is an instance of Plugin. - if (!$plugin instanceof Plugin) { - return false; - } - } - - return true; - }); + $resolver->setAllowedTypes('max_restarts', 'int'); return $resolver->resolve($options); } From 859f4c573c74fb7718ad818d9fd9c7d9624e7ab8 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 20 Nov 2018 16:04:45 +0100 Subject: [PATCH 003/123] [2.0] Remove plugin chain debug (#119) Complete removal of debug_plugins --- spec/PluginClientSpec.php | 43 --------------------------------------- src/PluginClient.php | 12 +---------- 2 files changed, 1 insertion(+), 54 deletions(-) diff --git a/spec/PluginClientSpec.php b/spec/PluginClientSpec.php index addb2e8..88406ae 100644 --- a/spec/PluginClientSpec.php +++ b/spec/PluginClientSpec.php @@ -87,47 +87,4 @@ function it_throws_loop_exception(HttpClient $httpClient, RequestInterface $requ $this->shouldThrow('Http\Client\Common\Exception\LoopException')->duringSendRequest($request); } - - function it_injects_debug_plugins(HttpClient $httpClient, ResponseInterface $response, RequestInterface $request, Plugin $plugin0, Plugin $plugin1, Plugin $debugPlugin) - { - $plugin0 - ->handleRequest( - $request, - Argument::type('callable'), - Argument::type('callable') - ) - ->shouldBeCalledTimes(1) - ->will(function ($args) { - return $args[1]($args[0]); - }) - ; - $plugin1 - ->handleRequest( - $request, - Argument::type('callable'), - Argument::type('callable') - ) - ->shouldBeCalledTimes(1) - ->will(function ($args) { - return $args[1]($args[0]); - }) - ; - - $debugPlugin - ->handleRequest( - $request, - Argument::type('callable'), - Argument::type('callable') - ) - ->shouldBeCalledTimes(3) - ->will(function ($args) { - return $args[1]($args[0]); - }) - ; - - $httpClient->sendRequest($request)->willReturn($response); - - $this->beConstructedWith($httpClient, [$plugin0, $plugin1], ['debug_plugins'=>[$debugPlugin]]); - $this->sendRequest($request); - } } diff --git a/src/PluginClient.php b/src/PluginClient.php index 70419e3..c3a5641 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -46,7 +46,6 @@ final class PluginClient implements HttpClient, HttpAsyncClient * @param array $options { * * @var int $max_restarts - * @var Plugin[] $debug_plugins an array of plugins that are injected between each normal plugin * } * * @throws \RuntimeException if client is not an instance of HttpClient or HttpAsyncClient @@ -131,16 +130,7 @@ private function createPluginChain($pluginList, callable $clientCallable) { $firstCallable = $lastCallable = $clientCallable; - /* - * Inject debug plugins between each plugin. - */ - $pluginListWithDebug = $this->options['debug_plugins']; - foreach ($pluginList as $plugin) { - $pluginListWithDebug[] = $plugin; - $pluginListWithDebug = array_merge($pluginListWithDebug, $this->options['debug_plugins']); - } - - while ($plugin = array_pop($pluginListWithDebug)) { + while ($plugin = array_pop($pluginList)) { $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) { return $plugin->handleRequest($request, $lastCallable, $firstCallable); }; From 94c98c68ad18e73c06d6050076c90d2a786be079 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 20 Nov 2018 16:37:01 +0100 Subject: [PATCH 004/123] Require PHP CS fixer and add basic configuration --- .gitignore | 1 + .php_cs | 25 ++++++++++++++++--------- composer.json | 5 ++++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 16b4a20..560b927 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /phpspec.yml /phpunit.xml /vendor/ +/.php_cs.cache diff --git a/.php_cs b/.php_cs index 23ba165..7da3701 100644 --- a/.php_cs +++ b/.php_cs @@ -1,13 +1,20 @@ setRules([ + '@PSR2' => true, + '@Symfony' => true, + 'array_syntax' => [ + 'syntax' => 'short', + ], +]); -use SLLH\StyleCIBridge\ConfigBridge; +$finder = PhpCsFixer\Finder::create(); +$finder->in([ + 'src', + 'spec' +]); -return ConfigBridge::create(); +$config->setFinder($finder); + +return $config; diff --git a/composer.json b/composer.json index fc3d1c1..882758b 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "guzzlehttp/psr7": "^1.4", "phpspec/phpspec": "^3.4 || ^4.2", "phpspec/prophecy": ">=1.8", - "sebastian/comparator": ">=2" + "sebastian/comparator": ">=2", + "friendsofphp/php-cs-fixer": "^2.2" }, "suggest": { "php-http/logger-plugin": "PSR-3 Logger plugin", @@ -35,6 +36,8 @@ } }, "scripts": { + "cs-check": "vendor/bin/php-cs-fixer fix --dry-run", + "cs-fix": "vendor/bin/php-cs-fixer fix", "test": "vendor/bin/phpspec run", "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" }, From 9f6d02cff27d988cfac948a88a041621d6945581 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 20 Nov 2018 16:39:58 +0100 Subject: [PATCH 005/123] Apply the base configuration --- spec/BatchClientSpec.php | 6 +- spec/BatchResultSpec.php | 10 +-- spec/EmulatedHttpAsyncClientSpec.php | 14 ++-- spec/EmulatedHttpClientSpec.php | 15 ++-- spec/Exception/BatchExceptionSpec.php | 11 ++- spec/FlexibleHttpClientSpec.php | 16 ++-- .../LeastUsedClientPoolSpec.php | 1 - spec/HttpClientPoolItemSpec.php | 12 +-- spec/HttpClientRouterSpec.php | 14 ++-- spec/HttpMethodsClientSpec.php | 27 ++++--- spec/Plugin/AddHostPluginSpec.php | 14 ++-- spec/Plugin/AddPathPluginSpec.php | 14 ++-- spec/Plugin/AuthenticationPluginSpec.php | 10 +-- spec/Plugin/BaseUriPluginSpec.php | 14 ++-- spec/Plugin/ContentLengthPluginSpec.php | 10 +-- spec/Plugin/ContentTypePluginSpec.php | 30 ++++---- spec/Plugin/CookiePluginSpec.php | 32 ++++---- spec/Plugin/DecoderPluginSpec.php | 22 +++--- spec/Plugin/ErrorPluginSpec.php | 20 ++--- spec/Plugin/HeaderAppendPluginSpec.php | 7 +- spec/Plugin/HeaderDefaultsPluginSpec.php | 5 +- spec/Plugin/HeaderRemovePluginSpec.php | 5 +- spec/Plugin/HeaderSetPluginSpec.php | 7 +- spec/Plugin/HistoryPluginSpec.php | 14 ++-- spec/Plugin/QueryDefaultsPluginSpec.php | 1 - spec/Plugin/RedirectPluginSpec.php | 74 +++++++++---------- spec/Plugin/RequestMatcherPluginSpec.php | 12 +-- spec/Plugin/RetryPluginSpec.php | 40 +++++----- spec/PluginClientFactorySpec.php | 6 +- spec/PluginClientSpec.php | 19 +++-- src/HttpClientRouter.php | 2 +- src/Plugin.php | 2 +- src/Plugin/BaseUriPlugin.php | 2 +- src/Plugin/ContentTypePlugin.php | 6 +- src/Plugin/ErrorPlugin.php | 2 +- src/Plugin/RetryPlugin.php | 4 +- 36 files changed, 235 insertions(+), 265 deletions(-) diff --git a/spec/BatchClientSpec.php b/spec/BatchClientSpec.php index 962f00a..8b7e96d 100644 --- a/spec/BatchClientSpec.php +++ b/spec/BatchClientSpec.php @@ -9,12 +9,12 @@ class BatchClientSpec extends ObjectBehavior { - function let(HttpClient $client) + public function let(HttpClient $client) { $this->beAnInstanceOf('Http\Client\Common\BatchClient', [$client]); } - function it_send_multiple_request_using_send_request(HttpClient $client, RequestInterface $request1, RequestInterface $request2, ResponseInterface $response1, ResponseInterface $response2) + public function it_send_multiple_request_using_send_request(HttpClient $client, RequestInterface $request1, RequestInterface $request2, ResponseInterface $response1, ResponseInterface $response2) { $client->sendRequest($request1)->willReturn($response1); $client->sendRequest($request2)->willReturn($response2); @@ -22,7 +22,7 @@ function it_send_multiple_request_using_send_request(HttpClient $client, Request $this->sendRequests([$request1, $request2])->shouldReturnAnInstanceOf('Http\Client\Common\BatchResult'); } - function it_throw_batch_exception_if_one_or_more_request_failed(HttpClient $client, RequestInterface $request1, RequestInterface $request2, ResponseInterface $response) + public function it_throw_batch_exception_if_one_or_more_request_failed(HttpClient $client, RequestInterface $request1, RequestInterface $request2, ResponseInterface $response) { $client->sendRequest($request1)->willReturn($response); $client->sendRequest($request2)->willThrow('Http\Client\Exception\HttpException'); diff --git a/spec/BatchResultSpec.php b/spec/BatchResultSpec.php index c4618ac..aa3bb14 100644 --- a/spec/BatchResultSpec.php +++ b/spec/BatchResultSpec.php @@ -9,12 +9,12 @@ class BatchResultSpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { $this->beAnInstanceOf('Http\Client\Common\BatchResult'); } - function it_is_immutable(RequestInterface $request, ResponseInterface $response) + public function it_is_immutable(RequestInterface $request, ResponseInterface $response) { $new = $this->addResponse($request, $response); @@ -23,7 +23,7 @@ function it_is_immutable(RequestInterface $request, ResponseInterface $response) $new->getResponses()->shouldReturn([$response]); } - function it_has_a_responses(RequestInterface $request, ResponseInterface $response) + public function it_has_a_responses(RequestInterface $request, ResponseInterface $response) { $new = $this->addResponse($request, $response); @@ -33,7 +33,7 @@ function it_has_a_responses(RequestInterface $request, ResponseInterface $respon $new->getResponses()->shouldReturn([$response]); } - function it_has_a_response_for_a_request(RequestInterface $request, ResponseInterface $response) + public function it_has_a_response_for_a_request(RequestInterface $request, ResponseInterface $response) { $new = $this->addResponse($request, $response); @@ -43,7 +43,7 @@ function it_has_a_response_for_a_request(RequestInterface $request, ResponseInte $new->isSuccessful($request)->shouldReturn(true); } - function it_keeps_exception_after_add_request(RequestInterface $request1, Exception $exception, RequestInterface $request2, ResponseInterface $response) + public function it_keeps_exception_after_add_request(RequestInterface $request1, Exception $exception, RequestInterface $request2, ResponseInterface $response) { $new = $this->addException($request1, $exception); $new = $new->addResponse($request2, $response); diff --git a/spec/EmulatedHttpAsyncClientSpec.php b/spec/EmulatedHttpAsyncClientSpec.php index b7a9edd..ca8a4d1 100644 --- a/spec/EmulatedHttpAsyncClientSpec.php +++ b/spec/EmulatedHttpAsyncClientSpec.php @@ -9,27 +9,27 @@ class EmulatedHttpAsyncClientSpec extends ObjectBehavior { - function let(HttpClient $httpClient) + public function let(HttpClient $httpClient) { $this->beConstructedWith($httpClient); } - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\EmulatedHttpAsyncClient'); } - function it_is_an_http_client() + public function it_is_an_http_client() { $this->shouldImplement('Http\Client\HttpClient'); } - function it_is_an_async_http_client() + public function it_is_an_async_http_client() { $this->shouldImplement('Http\Client\HttpAsyncClient'); } - function it_emulates_a_successful_request( + public function it_emulates_a_successful_request( HttpClient $httpClient, RequestInterface $request, ResponseInterface $response @@ -39,14 +39,14 @@ function it_emulates_a_successful_request( $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); } - function it_emulates_a_failed_request(HttpClient $httpClient, RequestInterface $request) + public function it_emulates_a_failed_request(HttpClient $httpClient, RequestInterface $request) { $httpClient->sendRequest($request)->willThrow('Http\Client\Exception\TransferException'); $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); } - function it_decorates_the_underlying_client( + public function it_decorates_the_underlying_client( HttpClient $httpClient, RequestInterface $request, ResponseInterface $response diff --git a/spec/EmulatedHttpClientSpec.php b/spec/EmulatedHttpClientSpec.php index 976f772..cf6e076 100644 --- a/spec/EmulatedHttpClientSpec.php +++ b/spec/EmulatedHttpClientSpec.php @@ -3,7 +3,6 @@ namespace spec\Http\Client\Common; use Http\Client\Exception\TransferException; -use Http\Client\HttpClient; use Http\Client\HttpAsyncClient; use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; @@ -12,27 +11,27 @@ class EmulatedHttpClientSpec extends ObjectBehavior { - function let(HttpAsyncClient $httpAsyncClient) + public function let(HttpAsyncClient $httpAsyncClient) { $this->beConstructedWith($httpAsyncClient); } - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\EmulatedHttpClient'); } - function it_is_an_http_client() + public function it_is_an_http_client() { $this->shouldImplement('Http\Client\HttpClient'); } - function it_is_an_async_http_client() + public function it_is_an_async_http_client() { $this->shouldImplement('Http\Client\HttpAsyncClient'); } - function it_emulates_a_successful_request( + public function it_emulates_a_successful_request( HttpAsyncClient $httpAsyncClient, RequestInterface $request, Promise $promise, @@ -47,7 +46,7 @@ function it_emulates_a_successful_request( $this->sendRequest($request)->shouldReturn($response); } - function it_emulates_a_failed_request(HttpAsyncClient $httpAsyncClient, RequestInterface $request, Promise $promise) + public function it_emulates_a_failed_request(HttpAsyncClient $httpAsyncClient, RequestInterface $request, Promise $promise) { $promise->wait()->shouldBeCalled(); $promise->getState()->willReturn(Promise::REJECTED); @@ -58,7 +57,7 @@ function it_emulates_a_failed_request(HttpAsyncClient $httpAsyncClient, RequestI $this->shouldThrow('Http\Client\Exception')->duringSendRequest($request); } - function it_decorates_the_underlying_client( + public function it_decorates_the_underlying_client( HttpAsyncClient $httpAsyncClient, RequestInterface $request, Promise $promise diff --git a/spec/Exception/BatchExceptionSpec.php b/spec/Exception/BatchExceptionSpec.php index fa8d8d6..0a32607 100644 --- a/spec/Exception/BatchExceptionSpec.php +++ b/spec/Exception/BatchExceptionSpec.php @@ -3,33 +3,32 @@ namespace spec\Http\Client\Common\Exception; use Http\Client\Common\BatchResult; -use Http\Client\Exception; use PhpSpec\ObjectBehavior; class BatchExceptionSpec extends ObjectBehavior { - function let() + public function let() { $batchResult = new BatchResult(); $this->beConstructedWith($batchResult); } - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\Exception\BatchException'); } - function it_is_a_runtime_exception() + public function it_is_a_runtime_exception() { $this->shouldHaveType('RuntimeException'); } - function it_is_an_exception() + public function it_is_an_exception() { $this->shouldImplement('Http\Client\Exception'); } - function it_has_a_batch_result() + public function it_has_a_batch_result() { $this->getResult()->shouldHaveType('Http\Client\Common\BatchResult'); } diff --git a/spec/FlexibleHttpClientSpec.php b/spec/FlexibleHttpClientSpec.php index 70e6e4d..769b4c3 100644 --- a/spec/FlexibleHttpClientSpec.php +++ b/spec/FlexibleHttpClientSpec.php @@ -11,34 +11,34 @@ class FlexibleHttpClientSpec extends ObjectBehavior { - function let(HttpClient $httpClient) + public function let(HttpClient $httpClient) { $this->beConstructedWith($httpClient); } - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\FlexibleHttpClient'); } - function it_is_an_http_client() + public function it_is_an_http_client() { $this->shouldImplement('Http\Client\HttpClient'); } - function it_is_an_async_http_client() + public function it_is_an_async_http_client() { $this->shouldImplement('Http\Client\HttpAsyncClient'); } - function it_throw_exception_if_invalid_client() + public function it_throw_exception_if_invalid_client() { $this->beConstructedWith(null); $this->shouldThrow('\LogicException')->duringInstantiation(); } - function it_emulates_an_async_client( + public function it_emulates_an_async_client( HttpClient $httpClient, RequestInterface $syncRequest, ResponseInterface $syncResponse, @@ -57,7 +57,7 @@ function it_emulates_an_async_client( $promise->wait()->shouldReturn($asyncResponse); } - function it_emulates_a_client( + public function it_emulates_a_client( HttpAsyncClient $httpAsyncClient, RequestInterface $asyncRequest, Promise $promise, @@ -75,7 +75,7 @@ function it_emulates_a_client( $this->sendRequest($syncRequest)->shouldReturn($syncResponse); } - function it_does_not_emulate_a_client($client, RequestInterface $syncRequest, RequestInterface $asyncRequest) + public function it_does_not_emulate_a_client($client, RequestInterface $syncRequest, RequestInterface $asyncRequest) { $client->implement('Http\Client\HttpClient'); $client->implement('Http\Client\HttpAsyncClient'); diff --git a/spec/HttpClientPool/LeastUsedClientPoolSpec.php b/spec/HttpClientPool/LeastUsedClientPoolSpec.php index 82855d9..8642c31 100644 --- a/spec/HttpClientPool/LeastUsedClientPoolSpec.php +++ b/spec/HttpClientPool/LeastUsedClientPoolSpec.php @@ -6,7 +6,6 @@ use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Promise\Promise; -use PhpSpec\Exception\Example\SkippingException; use PhpSpec\ObjectBehavior; use Prophecy\Argument; use Psr\Http\Message\RequestInterface; diff --git a/spec/HttpClientPoolItemSpec.php b/spec/HttpClientPoolItemSpec.php index 059ec8d..cba68d8 100644 --- a/spec/HttpClientPoolItemSpec.php +++ b/spec/HttpClientPoolItemSpec.php @@ -156,7 +156,7 @@ public function getState() public function wait($unwrap = true) { - if ($this->state === Promise::FULFILLED) { + if (Promise::FULFILLED === $this->state) { if (!$unwrap) { return; } @@ -164,7 +164,7 @@ public function wait($unwrap = true) return $this->response; } - if ($this->state === Promise::REJECTED) { + if (Promise::REJECTED === $this->state) { if (!$unwrap) { return; } @@ -175,7 +175,7 @@ public function wait($unwrap = true) while (count($this->queue) > 0) { $callbacks = array_shift($this->queue); - if ($this->response !== null) { + if (null !== $this->response) { try { $this->response = $callbacks[0]($this->response); $this->exception = null; @@ -183,7 +183,7 @@ public function wait($unwrap = true) $this->response = null; $this->exception = $exception; } - } elseif ($this->exception !== null) { + } elseif (null !== $this->exception) { try { $this->response = $callbacks[1]($this->exception); $this->exception = null; @@ -194,7 +194,7 @@ public function wait($unwrap = true) } } - if ($this->response !== null) { + if (null !== $this->response) { $this->state = Promise::FULFILLED; if ($unwrap) { @@ -202,7 +202,7 @@ public function wait($unwrap = true) } } - if ($this->exception !== null) { + if (null !== $this->exception) { $this->state = Promise::REJECTED; if ($unwrap) { diff --git a/spec/HttpClientRouterSpec.php b/spec/HttpClientRouterSpec.php index 1119722..e43acea 100644 --- a/spec/HttpClientRouterSpec.php +++ b/spec/HttpClientRouterSpec.php @@ -12,22 +12,22 @@ class HttpClientRouterSpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\HttpClientRouter'); } - function it_is_an_http_client() + public function it_is_an_http_client() { $this->shouldImplement('Http\Client\HttpClient'); } - function it_is_an_async_http_client() + public function it_is_an_async_http_client() { $this->shouldImplement('Http\Client\HttpAsyncClient'); } - function it_send_request(RequestMatcher $matcher, HttpClient $client, RequestInterface $request, ResponseInterface $response) + public function it_send_request(RequestMatcher $matcher, HttpClient $client, RequestInterface $request, ResponseInterface $response) { $this->addClient($client, $matcher); $matcher->matches($request)->willReturn(true); @@ -36,7 +36,7 @@ function it_send_request(RequestMatcher $matcher, HttpClient $client, RequestInt $this->sendRequest($request)->shouldReturn($response); } - function it_send_async_request(RequestMatcher $matcher, HttpAsyncClient $client, RequestInterface $request, Promise $promise) + public function it_send_async_request(RequestMatcher $matcher, HttpAsyncClient $client, RequestInterface $request, Promise $promise) { $this->addClient($client, $matcher); $matcher->matches($request)->willReturn(true); @@ -45,7 +45,7 @@ function it_send_async_request(RequestMatcher $matcher, HttpAsyncClient $client, $this->sendAsyncRequest($request)->shouldReturn($promise); } - function it_throw_exception_on_send_request(RequestMatcher $matcher, HttpClient $client, RequestInterface $request) + public function it_throw_exception_on_send_request(RequestMatcher $matcher, HttpClient $client, RequestInterface $request) { $this->addClient($client, $matcher); $matcher->matches($request)->willReturn(false); @@ -53,7 +53,7 @@ function it_throw_exception_on_send_request(RequestMatcher $matcher, HttpClient $this->shouldThrow('Http\Client\Exception\RequestException')->duringSendRequest($request); } - function it_throw_exception_on_send_async_request(RequestMatcher $matcher, HttpAsyncClient $client, RequestInterface $request) + public function it_throw_exception_on_send_async_request(RequestMatcher $matcher, HttpAsyncClient $client, RequestInterface $request) { $this->addClient($client, $matcher); $matcher->matches($request)->willReturn(false); diff --git a/spec/HttpMethodsClientSpec.php b/spec/HttpMethodsClientSpec.php index 07c0b47..b68c8c4 100644 --- a/spec/HttpMethodsClientSpec.php +++ b/spec/HttpMethodsClientSpec.php @@ -2,7 +2,6 @@ namespace spec\Http\Client\Common; -use Http\Client\BatchResult; use Http\Client\HttpClient; use Http\Client\Common\HttpMethodsClient; use Http\Message\MessageFactory; @@ -12,73 +11,73 @@ class HttpMethodsClientSpec extends ObjectBehavior { - function let(HttpClient $client, MessageFactory $messageFactory) + public function let(HttpClient $client, MessageFactory $messageFactory) { $this->beAnInstanceOf( 'spec\Http\Client\Common\HttpMethodsClientStub', [ $client, - $messageFactory + $messageFactory, ] ); } - function it_sends_a_get_request() + public function it_sends_a_get_request() { $data = HttpMethodsClientStub::$requestData; $this->get($data['uri'], $data['headers'])->shouldReturn(true); } - function it_sends_a_head_request() + public function it_sends_a_head_request() { $data = HttpMethodsClientStub::$requestData; $this->head($data['uri'], $data['headers'])->shouldReturn(true); } - function it_sends_a_trace_request() + public function it_sends_a_trace_request() { $data = HttpMethodsClientStub::$requestData; $this->trace($data['uri'], $data['headers'])->shouldReturn(true); } - function it_sends_a_post_request() + public function it_sends_a_post_request() { $data = HttpMethodsClientStub::$requestData; $this->post($data['uri'], $data['headers'], $data['body'])->shouldReturn(true); } - function it_sends_a_put_request() + public function it_sends_a_put_request() { $data = HttpMethodsClientStub::$requestData; $this->put($data['uri'], $data['headers'], $data['body'])->shouldReturn(true); } - function it_sends_a_patch_request() + public function it_sends_a_patch_request() { $data = HttpMethodsClientStub::$requestData; $this->patch($data['uri'], $data['headers'], $data['body'])->shouldReturn(true); } - function it_sends_a_delete_request() + public function it_sends_a_delete_request() { $data = HttpMethodsClientStub::$requestData; $this->delete($data['uri'], $data['headers'], $data['body'])->shouldReturn(true); } - function it_sends_a_options_request() + public function it_sends_a_options_request() { $data = HttpMethodsClientStub::$requestData; $this->options($data['uri'], $data['headers'], $data['body'])->shouldReturn(true); } - function it_sends_request_with_underlying_client(HttpClient $client, MessageFactory $messageFactory, RequestInterface $request, ResponseInterface $response) + public function it_sends_request_with_underlying_client(HttpClient $client, MessageFactory $messageFactory, RequestInterface $request, ResponseInterface $response) { $client->sendRequest($request)->shouldBeCalled()->willReturn($response); @@ -90,11 +89,11 @@ function it_sends_request_with_underlying_client(HttpClient $client, MessageFact class HttpMethodsClientStub extends HttpMethodsClient { public static $requestData = [ - 'uri' => '/uri', + 'uri' => '/uri', 'headers' => [ 'Content-Type' => 'text/plain', ], - 'body' => 'body' + 'body' => 'body', ]; /** diff --git a/spec/Plugin/AddHostPluginSpec.php b/spec/Plugin/AddHostPluginSpec.php index caf4f21..a6630bd 100644 --- a/spec/Plugin/AddHostPluginSpec.php +++ b/spec/Plugin/AddHostPluginSpec.php @@ -2,34 +2,32 @@ namespace spec\Http\Client\Common\Plugin; -use Http\Message\StreamFactory; -use Http\Message\UriFactory; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; use PhpSpec\ObjectBehavior; class AddHostPluginSpec extends ObjectBehavior { - function let(UriInterface $uri) + public function let(UriInterface $uri) { $this->beConstructedWith($uri); } - function it_is_initializable(UriInterface $uri) + public function it_is_initializable(UriInterface $uri) { $uri->getHost()->shouldBeCalled()->willReturn('example.com'); $this->shouldHaveType('Http\Client\Common\Plugin\AddHostPlugin'); } - function it_is_a_plugin(UriInterface $uri) + public function it_is_a_plugin(UriInterface $uri) { $uri->getHost()->shouldBeCalled()->willReturn('example.com'); $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_adds_domain( + public function it_adds_domain( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -50,7 +48,7 @@ function it_adds_domain( $this->handleRequest($request, function () {}, function () {}); } - function it_replaces_domain( + public function it_replaces_domain( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -70,7 +68,7 @@ function it_replaces_domain( $this->handleRequest($request, function () {}, function () {}); } - function it_does_nothing_when_domain_exists( + public function it_does_nothing_when_domain_exists( RequestInterface $request, UriInterface $host, UriInterface $uri diff --git a/spec/Plugin/AddPathPluginSpec.php b/spec/Plugin/AddPathPluginSpec.php index a7ec9b5..c95301c 100644 --- a/spec/Plugin/AddPathPluginSpec.php +++ b/spec/Plugin/AddPathPluginSpec.php @@ -2,34 +2,32 @@ namespace spec\Http\Client\Common\Plugin; -use Http\Message\StreamFactory; -use Http\Message\UriFactory; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; use PhpSpec\ObjectBehavior; class AddPathPluginSpec extends ObjectBehavior { - function let(UriInterface $uri) + public function let(UriInterface $uri) { $this->beConstructedWith($uri); } - function it_is_initializable(UriInterface $uri) + public function it_is_initializable(UriInterface $uri) { $uri->getPath()->shouldBeCalled()->willReturn('/api'); $this->shouldHaveType('Http\Client\Common\Plugin\AddPathPlugin'); } - function it_is_a_plugin(UriInterface $uri) + public function it_is_a_plugin(UriInterface $uri) { $uri->getPath()->shouldBeCalled()->willReturn('/api'); $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_adds_path( + public function it_adds_path( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -46,7 +44,7 @@ function it_adds_path( $this->handleRequest($request, function () {}, function () {}); } - function it_throws_exception_on_trailing_slash(UriInterface $host) + public function it_throws_exception_on_trailing_slash(UriInterface $host) { $host->getPath()->shouldBeCalled()->willReturn('/api/'); @@ -54,7 +52,7 @@ function it_throws_exception_on_trailing_slash(UriInterface $host) $this->shouldThrow('\LogicException')->duringInstantiation(); } - function it_throws_exception_on_empty_path(UriInterface $host) + public function it_throws_exception_on_empty_path(UriInterface $host) { $host->getPath()->shouldBeCalled()->willReturn(''); diff --git a/spec/Plugin/AuthenticationPluginSpec.php b/spec/Plugin/AuthenticationPluginSpec.php index 02d1187..549b507 100644 --- a/spec/Plugin/AuthenticationPluginSpec.php +++ b/spec/Plugin/AuthenticationPluginSpec.php @@ -10,26 +10,26 @@ class AuthenticationPluginSpec extends ObjectBehavior { - function let(Authentication $authentication) + public function let(Authentication $authentication) { $this->beConstructedWith($authentication); } - function it_is_initializable(Authentication $authentication) + public function it_is_initializable(Authentication $authentication) { $this->shouldHaveType('Http\Client\Common\Plugin\AuthenticationPlugin'); } - function it_is_a_plugin() + public function it_is_a_plugin() { $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_sends_an_authenticated_request(Authentication $authentication, RequestInterface $notAuthedRequest, RequestInterface $authedRequest, Promise $promise) + public function it_sends_an_authenticated_request(Authentication $authentication, RequestInterface $notAuthedRequest, RequestInterface $authedRequest, Promise $promise) { $authentication->authenticate($notAuthedRequest)->willReturn($authedRequest); - $next = function (RequestInterface $request) use($authedRequest, $promise) { + $next = function (RequestInterface $request) use ($authedRequest, $promise) { if (Argument::is($authedRequest->getWrappedObject())->scoreArgument($request)) { return $promise->getWrappedObject(); } diff --git a/spec/Plugin/BaseUriPluginSpec.php b/spec/Plugin/BaseUriPluginSpec.php index 2faf769..e2f8c46 100644 --- a/spec/Plugin/BaseUriPluginSpec.php +++ b/spec/Plugin/BaseUriPluginSpec.php @@ -2,20 +2,18 @@ namespace spec\Http\Client\Common\Plugin; -use Http\Message\StreamFactory; -use Http\Message\UriFactory; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; use PhpSpec\ObjectBehavior; class BaseUriPluginSpec extends ObjectBehavior { - function let(UriInterface $uri) + public function let(UriInterface $uri) { $this->beConstructedWith($uri); } - function it_is_initializable(UriInterface $uri) + public function it_is_initializable(UriInterface $uri) { $uri->getHost()->shouldBeCalled()->willReturn('example.com'); $uri->getPath()->shouldBeCalled()->willReturn('/api'); @@ -23,7 +21,7 @@ function it_is_initializable(UriInterface $uri) $this->shouldHaveType('Http\Client\Common\Plugin\BaseUriPlugin'); } - function it_is_a_plugin(UriInterface $uri) + public function it_is_a_plugin(UriInterface $uri) { $uri->getHost()->shouldBeCalled()->willReturn('example.com'); $uri->getPath()->shouldBeCalled()->willReturn('/api'); @@ -31,7 +29,7 @@ function it_is_a_plugin(UriInterface $uri) $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_adds_domain_and_path( + public function it_adds_domain_and_path( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -55,7 +53,7 @@ function it_adds_domain_and_path( $this->handleRequest($request, function () {}, function () {}); } - function it_adds_domain( + public function it_adds_domain( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -77,7 +75,7 @@ function it_adds_domain( $this->handleRequest($request, function () {}, function () {}); } - function it_replaces_domain_and_adds_path( + public function it_replaces_domain_and_adds_path( RequestInterface $request, UriInterface $host, UriInterface $uri diff --git a/spec/Plugin/ContentLengthPluginSpec.php b/spec/Plugin/ContentLengthPluginSpec.php index 4ec2ba7..6624f6c 100644 --- a/spec/Plugin/ContentLengthPluginSpec.php +++ b/spec/Plugin/ContentLengthPluginSpec.php @@ -10,17 +10,17 @@ class ContentLengthPluginSpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\Plugin\ContentLengthPlugin'); } - function it_is_a_plugin() + public function it_is_a_plugin() { $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_adds_content_length_header(RequestInterface $request, StreamInterface $stream) + public function it_adds_content_length_header(RequestInterface $request, StreamInterface $stream) { $request->hasHeader('Content-Length')->shouldBeCalled()->willReturn(false); $request->getBody()->shouldBeCalled()->willReturn($stream); @@ -30,9 +30,9 @@ function it_adds_content_length_header(RequestInterface $request, StreamInterfac $this->handleRequest($request, function () {}, function () {}); } - function it_streams_chunked_if_no_size(RequestInterface $request, StreamInterface $stream) + public function it_streams_chunked_if_no_size(RequestInterface $request, StreamInterface $stream) { - if(defined('HHVM_VERSION')) { + if (defined('HHVM_VERSION')) { throw new SkippingException('Skipping test on hhvm, as there is no chunk encoding on hhvm'); } diff --git a/spec/Plugin/ContentTypePluginSpec.php b/spec/Plugin/ContentTypePluginSpec.php index 3df7d87..d2a6ffa 100644 --- a/spec/Plugin/ContentTypePluginSpec.php +++ b/spec/Plugin/ContentTypePluginSpec.php @@ -2,25 +2,22 @@ namespace spec\Http\Client\Common\Plugin; -use PhpSpec\Exception\Example\SkippingException; use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; class ContentTypePluginSpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\Plugin\ContentTypePlugin'); } - function it_is_a_plugin() + public function it_is_a_plugin() { $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_adds_json_content_type_header(RequestInterface $request) + public function it_adds_json_content_type_header(RequestInterface $request) { $request->hasHeader('Content-Type')->shouldBeCalled()->willReturn(false); $request->getBody()->shouldBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for(json_encode(['foo' => 'bar']))); @@ -29,7 +26,7 @@ function it_adds_json_content_type_header(RequestInterface $request) $this->handleRequest($request, function () {}, function () {}); } - function it_adds_xml_content_type_header(RequestInterface $request) + public function it_adds_xml_content_type_header(RequestInterface $request) { $request->hasHeader('Content-Type')->shouldBeCalled()->willReturn(false); $request->getBody()->shouldBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for('bar')); @@ -38,7 +35,7 @@ function it_adds_xml_content_type_header(RequestInterface $request) $this->handleRequest($request, function () {}, function () {}); } - function it_does_not_set_content_type_header(RequestInterface $request) + public function it_does_not_set_content_type_header(RequestInterface $request) { $request->hasHeader('Content-Type')->shouldBeCalled()->willReturn(false); $request->getBody()->shouldBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for('foo')); @@ -47,7 +44,7 @@ function it_does_not_set_content_type_header(RequestInterface $request) $this->handleRequest($request, function () {}, function () {}); } - function it_does_not_set_content_type_header_if_already_one(RequestInterface $request) + public function it_does_not_set_content_type_header_if_already_one(RequestInterface $request) { $request->hasHeader('Content-Type')->shouldBeCalled()->willReturn(true); $request->getBody()->shouldNotBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for('foo')); @@ -56,7 +53,7 @@ function it_does_not_set_content_type_header_if_already_one(RequestInterface $re $this->handleRequest($request, function () {}, function () {}); } - function it_does_not_set_content_type_header_if_size_0_or_unknown(RequestInterface $request) + public function it_does_not_set_content_type_header_if_size_0_or_unknown(RequestInterface $request) { $request->hasHeader('Content-Type')->shouldBeCalled()->willReturn(false); $request->getBody()->shouldBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for()); @@ -65,10 +62,10 @@ function it_does_not_set_content_type_header_if_size_0_or_unknown(RequestInterfa $this->handleRequest($request, function () {}, function () {}); } - function it_adds_xml_content_type_header_if_size_limit_is_not_reached_using_default_value(RequestInterface $request) + public function it_adds_xml_content_type_header_if_size_limit_is_not_reached_using_default_value(RequestInterface $request) { $this->beConstructedWith([ - 'skip_detection' => true + 'skip_detection' => true, ]); $request->hasHeader('Content-Type')->shouldBeCalled()->willReturn(false); @@ -78,11 +75,11 @@ function it_adds_xml_content_type_header_if_size_limit_is_not_reached_using_defa $this->handleRequest($request, function () {}, function () {}); } - function it_adds_xml_content_type_header_if_size_limit_is_not_reached(RequestInterface $request) + public function it_adds_xml_content_type_header_if_size_limit_is_not_reached(RequestInterface $request) { $this->beConstructedWith([ 'skip_detection' => true, - 'size_limit' => 32000000 + 'size_limit' => 32000000, ]); $request->hasHeader('Content-Type')->shouldBeCalled()->willReturn(false); @@ -92,11 +89,11 @@ function it_adds_xml_content_type_header_if_size_limit_is_not_reached(RequestInt $this->handleRequest($request, function () {}, function () {}); } - function it_does_not_set_content_type_header_if_size_limit_is_reached(RequestInterface $request) + public function it_does_not_set_content_type_header_if_size_limit_is_reached(RequestInterface $request) { $this->beConstructedWith([ 'skip_detection' => true, - 'size_limit' => 8 + 'size_limit' => 8, ]); $request->hasHeader('Content-Type')->shouldBeCalled()->willReturn(false); @@ -105,5 +102,4 @@ function it_does_not_set_content_type_header_if_size_limit_is_reached(RequestInt $this->handleRequest($request, function () {}, function () {}); } - } diff --git a/spec/Plugin/CookiePluginSpec.php b/spec/Plugin/CookiePluginSpec.php index 2bb47e7..1f021b1 100644 --- a/spec/Plugin/CookiePluginSpec.php +++ b/spec/Plugin/CookiePluginSpec.php @@ -16,24 +16,24 @@ class CookiePluginSpec extends ObjectBehavior { private $cookieJar; - function let() + public function let() { $this->cookieJar = new CookieJar(); $this->beConstructedWith($this->cookieJar); } - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\Plugin\CookiePlugin'); } - function it_is_a_plugin() + public function it_is_a_plugin() { $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_loads_cookie(RequestInterface $request, UriInterface $uri, Promise $promise) + public function it_loads_cookie(RequestInterface $request, UriInterface $uri, Promise $promise) { $cookie = new Cookie('name', 'value', 86400, 'test.com'); $this->cookieJar->addCookie($cookie); @@ -51,7 +51,7 @@ function it_loads_cookie(RequestInterface $request, UriInterface $uri, Promise $ }, function () {}); } - function it_combines_multiple_cookies_into_one_header(RequestInterface $request, UriInterface $uri, Promise $promise) + public function it_combines_multiple_cookies_into_one_header(RequestInterface $request, UriInterface $uri, Promise $promise) { $cookie = new Cookie('name', 'value', 86400, 'test.com'); $cookie2 = new Cookie('name2', 'value2', 86400, 'test.com'); @@ -72,7 +72,7 @@ function it_combines_multiple_cookies_into_one_header(RequestInterface $request, }, function () {}); } - function it_does_not_load_cookie_if_expired(RequestInterface $request, UriInterface $uri, Promise $promise) + public function it_does_not_load_cookie_if_expired(RequestInterface $request, UriInterface $uri, Promise $promise) { $cookie = new Cookie('name', 'value', null, 'test.com', false, false, null, (new \DateTime())->modify('-1 day')); $this->cookieJar->addCookie($cookie); @@ -86,7 +86,7 @@ function it_does_not_load_cookie_if_expired(RequestInterface $request, UriInterf }, function () {}); } - function it_does_not_load_cookie_if_domain_does_not_match(RequestInterface $request, UriInterface $uri, Promise $promise) + public function it_does_not_load_cookie_if_domain_does_not_match(RequestInterface $request, UriInterface $uri, Promise $promise) { $cookie = new Cookie('name', 'value', 86400, 'test2.com'); $this->cookieJar->addCookie($cookie); @@ -103,7 +103,7 @@ function it_does_not_load_cookie_if_domain_does_not_match(RequestInterface $requ }, function () {}); } - function it_does_not_load_cookie_on_hackish_domains(RequestInterface $request, UriInterface $uri, Promise $promise) + public function it_does_not_load_cookie_on_hackish_domains(RequestInterface $request, UriInterface $uri, Promise $promise) { $hackishDomains = [ 'hacktest.com', @@ -126,7 +126,7 @@ function it_does_not_load_cookie_on_hackish_domains(RequestInterface $request, U } } - function it_loads_cookie_on_subdomains(RequestInterface $request, UriInterface $uri, Promise $promise) + public function it_loads_cookie_on_subdomains(RequestInterface $request, UriInterface $uri, Promise $promise) { $cookie = new Cookie('name', 'value', 86400, 'test.com'); $this->cookieJar->addCookie($cookie); @@ -144,7 +144,7 @@ function it_loads_cookie_on_subdomains(RequestInterface $request, UriInterface $ }, function () {}); } - function it_does_not_load_cookie_if_path_does_not_match(RequestInterface $request, UriInterface $uri, Promise $promise) + public function it_does_not_load_cookie_if_path_does_not_match(RequestInterface $request, UriInterface $uri, Promise $promise) { $cookie = new Cookie('name', 'value', 86400, 'test.com', '/sub'); $this->cookieJar->addCookie($cookie); @@ -162,7 +162,7 @@ function it_does_not_load_cookie_if_path_does_not_match(RequestInterface $reques }, function () {}); } - function it_does_not_load_cookie_when_cookie_is_secure(RequestInterface $request, UriInterface $uri, Promise $promise) + public function it_does_not_load_cookie_when_cookie_is_secure(RequestInterface $request, UriInterface $uri, Promise $promise) { $cookie = new Cookie('name', 'value', 86400, 'test.com', null, true); $this->cookieJar->addCookie($cookie); @@ -181,7 +181,7 @@ function it_does_not_load_cookie_when_cookie_is_secure(RequestInterface $request }, function () {}); } - function it_loads_cookie_when_cookie_is_secure(RequestInterface $request, UriInterface $uri, Promise $promise) + public function it_loads_cookie_when_cookie_is_secure(RequestInterface $request, UriInterface $uri, Promise $promise) { $cookie = new Cookie('name', 'value', 86400, 'test.com', null, true); $this->cookieJar->addCookie($cookie); @@ -200,7 +200,7 @@ function it_loads_cookie_when_cookie_is_secure(RequestInterface $request, UriInt }, function () {}); } - function it_saves_cookie(RequestInterface $request, ResponseInterface $response, UriInterface $uri) + public function it_saves_cookie(RequestInterface $request, ResponseInterface $response, UriInterface $uri) { $next = function () use ($response) { return new HttpFulfilledPromise($response->getWrappedObject()); @@ -208,7 +208,7 @@ function it_saves_cookie(RequestInterface $request, ResponseInterface $response, $response->hasHeader('Set-Cookie')->willReturn(true); $response->getHeader('Set-Cookie')->willReturn([ - 'cookie=value; expires=Tuesday, 31-Mar-99 07:42:12 GMT; Max-Age=60; path=/; domain=test.com; secure; HttpOnly' + 'cookie=value; expires=Tuesday, 31-Mar-99 07:42:12 GMT; Max-Age=60; path=/; domain=test.com; secure; HttpOnly', ]); $request->getUri()->willReturn($uri); @@ -220,7 +220,7 @@ function it_saves_cookie(RequestInterface $request, ResponseInterface $response, $promise->wait()->shouldReturnAnInstanceOf('Psr\Http\Message\ResponseInterface'); } - function it_throws_exception_on_invalid_expires_date( + public function it_throws_exception_on_invalid_expires_date( RequestInterface $request, ResponseInterface $response, UriInterface $uri @@ -231,7 +231,7 @@ function it_throws_exception_on_invalid_expires_date( $response->hasHeader('Set-Cookie')->willReturn(true); $response->getHeader('Set-Cookie')->willReturn([ - 'cookie=value; expires=i-am-an-invalid-date;' + 'cookie=value; expires=i-am-an-invalid-date;', ]); $request->getUri()->willReturn($uri); diff --git a/spec/Plugin/DecoderPluginSpec.php b/spec/Plugin/DecoderPluginSpec.php index 7543027..4de938c 100644 --- a/spec/Plugin/DecoderPluginSpec.php +++ b/spec/Plugin/DecoderPluginSpec.php @@ -12,25 +12,25 @@ class DecoderPluginSpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\Plugin\DecoderPlugin'); } - function it_is_a_plugin() + public function it_is_a_plugin() { $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_decodes(RequestInterface $request, ResponseInterface $response, StreamInterface $stream) + public function it_decodes(RequestInterface $request, ResponseInterface $response, StreamInterface $stream) { - if(defined('HHVM_VERSION')) { + if (defined('HHVM_VERSION')) { throw new SkippingException('Skipping test on hhvm, as there is no chunk encoding on hhvm'); } $request->withHeader('TE', ['gzip', 'deflate', 'chunked'])->shouldBeCalled()->willReturn($request); $request->withHeader('Accept-Encoding', ['gzip', 'deflate'])->shouldBeCalled()->willReturn($request); - $next = function () use($response) { + $next = function () use ($response) { return new HttpFulfilledPromise($response->getWrappedObject()); }; @@ -48,11 +48,11 @@ function it_decodes(RequestInterface $request, ResponseInterface $response, Stre $this->handleRequest($request, $next, function () {}); } - function it_decodes_gzip(RequestInterface $request, ResponseInterface $response, StreamInterface $stream) + public function it_decodes_gzip(RequestInterface $request, ResponseInterface $response, StreamInterface $stream) { $request->withHeader('TE', ['gzip', 'deflate', 'chunked'])->shouldBeCalled()->willReturn($request); $request->withHeader('Accept-Encoding', ['gzip', 'deflate'])->shouldBeCalled()->willReturn($request); - $next = function () use($response) { + $next = function () use ($response) { return new HttpFulfilledPromise($response->getWrappedObject()); }; @@ -70,11 +70,11 @@ function it_decodes_gzip(RequestInterface $request, ResponseInterface $response, $this->handleRequest($request, $next, function () {}); } - function it_decodes_deflate(RequestInterface $request, ResponseInterface $response, StreamInterface $stream) + public function it_decodes_deflate(RequestInterface $request, ResponseInterface $response, StreamInterface $stream) { $request->withHeader('TE', ['gzip', 'deflate', 'chunked'])->shouldBeCalled()->willReturn($request); $request->withHeader('Accept-Encoding', ['gzip', 'deflate'])->shouldBeCalled()->willReturn($request); - $next = function () use($response) { + $next = function () use ($response) { return new HttpFulfilledPromise($response->getWrappedObject()); }; @@ -92,13 +92,13 @@ function it_decodes_deflate(RequestInterface $request, ResponseInterface $respon $this->handleRequest($request, $next, function () {}); } - function it_does_not_decode_with_content_encoding(RequestInterface $request, ResponseInterface $response) + public function it_does_not_decode_with_content_encoding(RequestInterface $request, ResponseInterface $response) { $this->beConstructedWith(['use_content_encoding' => false]); $request->withHeader('TE', ['gzip', 'deflate', 'chunked'])->shouldBeCalled()->willReturn($request); $request->withHeader('Accept-Encoding', ['gzip', 'deflate'])->shouldNotBeCalled(); - $next = function () use($response) { + $next = function () use ($response) { return new HttpFulfilledPromise($response->getWrappedObject()); }; diff --git a/spec/Plugin/ErrorPluginSpec.php b/spec/Plugin/ErrorPluginSpec.php index 20fcc25..5a9e90a 100644 --- a/spec/Plugin/ErrorPluginSpec.php +++ b/spec/Plugin/ErrorPluginSpec.php @@ -10,22 +10,22 @@ class ErrorPluginSpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { $this->beAnInstanceOf('Http\Client\Common\Plugin\ErrorPlugin'); } - function it_is_a_plugin() + public function it_is_a_plugin() { $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_throw_client_error_exception_on_4xx_error(RequestInterface $request, ResponseInterface $response) + public function it_throw_client_error_exception_on_4xx_error(RequestInterface $request, ResponseInterface $response) { $response->getStatusCode()->willReturn('400'); $response->getReasonPhrase()->willReturn('Bad request'); - $next = function (RequestInterface $receivedRequest) use($request, $response) { + $next = function (RequestInterface $receivedRequest) use ($request, $response) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($response->getWrappedObject()); } @@ -36,14 +36,14 @@ function it_throw_client_error_exception_on_4xx_error(RequestInterface $request, $promise->shouldThrow('Http\Client\Common\Exception\ClientErrorException')->duringWait(); } - function it_does_not_throw_client_error_exception_on_4xx_error_if_only_server_exception(RequestInterface $request, ResponseInterface $response) + public function it_does_not_throw_client_error_exception_on_4xx_error_if_only_server_exception(RequestInterface $request, ResponseInterface $response) { $this->beConstructedWith(['only_server_exception' => true]); $response->getStatusCode()->willReturn('400'); $response->getReasonPhrase()->willReturn('Bad request'); - $next = function (RequestInterface $receivedRequest) use($request, $response) { + $next = function (RequestInterface $receivedRequest) use ($request, $response) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($response->getWrappedObject()); } @@ -52,12 +52,12 @@ function it_does_not_throw_client_error_exception_on_4xx_error_if_only_server_ex $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); } - function it_throw_server_error_exception_on_5xx_error(RequestInterface $request, ResponseInterface $response) + public function it_throw_server_error_exception_on_5xx_error(RequestInterface $request, ResponseInterface $response) { $response->getStatusCode()->willReturn('500'); $response->getReasonPhrase()->willReturn('Server error'); - $next = function (RequestInterface $receivedRequest) use($request, $response) { + $next = function (RequestInterface $receivedRequest) use ($request, $response) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($response->getWrappedObject()); } @@ -68,11 +68,11 @@ function it_throw_server_error_exception_on_5xx_error(RequestInterface $request, $promise->shouldThrow('Http\Client\Common\Exception\ServerErrorException')->duringWait(); } - function it_returns_response(RequestInterface $request, ResponseInterface $response) + public function it_returns_response(RequestInterface $request, ResponseInterface $response) { $response->getStatusCode()->willReturn('200'); - $next = function (RequestInterface $receivedRequest) use($request, $response) { + $next = function (RequestInterface $receivedRequest) use ($request, $response) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($response->getWrappedObject()); } diff --git a/spec/Plugin/HeaderAppendPluginSpec.php b/spec/Plugin/HeaderAppendPluginSpec.php index 24b8565..bb10de1 100644 --- a/spec/Plugin/HeaderAppendPluginSpec.php +++ b/spec/Plugin/HeaderAppendPluginSpec.php @@ -2,11 +2,8 @@ namespace spec\Http\Client\Common\Plugin; -use PhpSpec\Exception\Example\SkippingException; use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; class HeaderAppendPluginSpec extends ObjectBehavior { @@ -25,8 +22,8 @@ public function it_is_a_plugin() public function it_appends_the_header(RequestInterface $request) { $this->beConstructedWith([ - 'foo'=>'bar', - 'baz'=>'qux' + 'foo' => 'bar', + 'baz' => 'qux', ]); $request->withAddedHeader('foo', 'bar')->shouldBeCalled()->willReturn($request); diff --git a/spec/Plugin/HeaderDefaultsPluginSpec.php b/spec/Plugin/HeaderDefaultsPluginSpec.php index 341f1a5..cd407e3 100644 --- a/spec/Plugin/HeaderDefaultsPluginSpec.php +++ b/spec/Plugin/HeaderDefaultsPluginSpec.php @@ -2,11 +2,8 @@ namespace spec\Http\Client\Common\Plugin; -use PhpSpec\Exception\Example\SkippingException; use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; class HeaderDefaultsPluginSpec extends ObjectBehavior { @@ -26,7 +23,7 @@ public function it_sets_the_default_header(RequestInterface $request) { $this->beConstructedWith([ 'foo' => 'bar', - 'baz' => 'qux' + 'baz' => 'qux', ]); $request->hasHeader('foo')->shouldBeCalled()->willReturn(false); diff --git a/spec/Plugin/HeaderRemovePluginSpec.php b/spec/Plugin/HeaderRemovePluginSpec.php index 9ea2752..ec6b069 100644 --- a/spec/Plugin/HeaderRemovePluginSpec.php +++ b/spec/Plugin/HeaderRemovePluginSpec.php @@ -2,11 +2,8 @@ namespace spec\Http\Client\Common\Plugin; -use PhpSpec\Exception\Example\SkippingException; use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; class HeaderRemovePluginSpec extends ObjectBehavior { @@ -26,7 +23,7 @@ public function it_removes_the_header(RequestInterface $request) { $this->beConstructedWith([ 'foo', - 'baz' + 'baz', ]); $request->hasHeader('foo')->shouldBeCalled()->willReturn(false); diff --git a/spec/Plugin/HeaderSetPluginSpec.php b/spec/Plugin/HeaderSetPluginSpec.php index f4a340c..4e4efd0 100644 --- a/spec/Plugin/HeaderSetPluginSpec.php +++ b/spec/Plugin/HeaderSetPluginSpec.php @@ -2,11 +2,8 @@ namespace spec\Http\Client\Common\Plugin; -use PhpSpec\Exception\Example\SkippingException; use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; class HeaderSetPluginSpec extends ObjectBehavior { @@ -25,8 +22,8 @@ public function it_is_a_plugin() public function it_set_the_header(RequestInterface $request) { $this->beConstructedWith([ - 'foo'=>'bar', - 'baz'=>'qux' + 'foo' => 'bar', + 'baz' => 'qux', ]); $request->withHeader('foo', 'bar')->shouldBeCalled()->willReturn($request); diff --git a/spec/Plugin/HistoryPluginSpec.php b/spec/Plugin/HistoryPluginSpec.php index 24e7f51..77682a7 100644 --- a/spec/Plugin/HistoryPluginSpec.php +++ b/spec/Plugin/HistoryPluginSpec.php @@ -13,24 +13,24 @@ class HistoryPluginSpec extends ObjectBehavior { - function let(Journal $journal) + public function let(Journal $journal) { $this->beConstructedWith($journal); } - function it_is_initializable() + public function it_is_initializable() { $this->beAnInstanceOf('Http\Client\Common\Plugin\JournalPlugin'); } - function it_is_a_plugin() + public function it_is_a_plugin() { $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_records_success(Journal $journal, RequestInterface $request, ResponseInterface $response) + public function it_records_success(Journal $journal, RequestInterface $request, ResponseInterface $response) { - $next = function (RequestInterface $receivedRequest) use($request, $response) { + $next = function (RequestInterface $receivedRequest) use ($request, $response) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($response->getWrappedObject()); } @@ -41,10 +41,10 @@ function it_records_success(Journal $journal, RequestInterface $request, Respons $this->handleRequest($request, $next, function () {}); } - function it_records_failure(Journal $journal, RequestInterface $request) + public function it_records_failure(Journal $journal, RequestInterface $request) { $exception = new TransferException(); - $next = function (RequestInterface $receivedRequest) use($request, $exception) { + $next = function (RequestInterface $receivedRequest) use ($request, $exception) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpRejectedPromise($exception); } diff --git a/spec/Plugin/QueryDefaultsPluginSpec.php b/spec/Plugin/QueryDefaultsPluginSpec.php index 82d9125..aa50f81 100644 --- a/spec/Plugin/QueryDefaultsPluginSpec.php +++ b/spec/Plugin/QueryDefaultsPluginSpec.php @@ -2,7 +2,6 @@ namespace spec\Http\Client\Common\Plugin; -use Http\Client\Common\Plugin; use Psr\Http\Message\RequestInterface; use PhpSpec\ObjectBehavior; use Psr\Http\Message\UriInterface; diff --git a/spec/Plugin/RedirectPluginSpec.php b/spec/Plugin/RedirectPluginSpec.php index 97197e1..283bfb5 100644 --- a/spec/Plugin/RedirectPluginSpec.php +++ b/spec/Plugin/RedirectPluginSpec.php @@ -13,17 +13,17 @@ class RedirectPluginSpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\Plugin\RedirectPlugin'); } - function it_is_a_plugin() + public function it_is_a_plugin() { $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_redirects_on_302( + public function it_redirects_on_302( UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, @@ -48,14 +48,13 @@ function it_redirects_on_302( $modifiedRequest->getUri()->willReturn($uriRedirect); $modifiedRequest->getMethod()->willReturn('GET'); - - $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) { + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); } }; - $first = function (RequestInterface $receivedRequest) use($modifiedRequest, $promise) { + $first = function (RequestInterface $receivedRequest) use ($modifiedRequest, $promise) { if (Argument::is($modifiedRequest->getWrappedObject())->scoreArgument($receivedRequest)) { return $promise->getWrappedObject(); } @@ -69,7 +68,7 @@ function it_redirects_on_302( $finalPromise->wait()->shouldReturn($finalResponse); } - function it_use_storage_on_301(UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, RequestInterface $modifiedRequest) + public function it_use_storage_on_301(UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, RequestInterface $modifiedRequest) { $this->beAnInstanceOf('spec\Http\Client\Common\Plugin\RedirectPluginStub'); $this->beConstructedWith($uriRedirect, '/original', '301'); @@ -90,7 +89,7 @@ function it_use_storage_on_301(UriInterface $uri, UriInterface $uriRedirect, Req $this->handleRequest($request, $next, function () {}); } - function it_stores_a_301( + public function it_stores_a_301( UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, @@ -99,7 +98,6 @@ function it_stores_a_301( ResponseInterface $finalResponse, Promise $promise ) { - $this->beAnInstanceOf('spec\Http\Client\Common\Plugin\RedirectPluginStub'); $this->beConstructedWith($uriRedirect, '', '301'); @@ -121,13 +119,13 @@ function it_stores_a_301( $uriRedirect->__toString()->willReturn('/redirect'); - $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) { + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); } }; - $first = function (RequestInterface $receivedRequest) use($modifiedRequest, $promise) { + $first = function (RequestInterface $receivedRequest) use ($modifiedRequest, $promise) { if (Argument::is($modifiedRequest->getWrappedObject())->scoreArgument($receivedRequest)) { return $promise->getWrappedObject(); } @@ -140,7 +138,7 @@ function it_stores_a_301( $this->hasStorage('/301-url')->shouldReturn(true); } - function it_replace_full_url( + public function it_replace_full_url( UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, @@ -171,13 +169,13 @@ function it_replace_full_url( $uriRedirect->__toString()->willReturn('/redirect'); - $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) { + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); } }; - $first = function (RequestInterface $receivedRequest) use($modifiedRequest, $promise) { + $first = function (RequestInterface $receivedRequest) use ($modifiedRequest, $promise) { if (Argument::is($modifiedRequest->getWrappedObject())->scoreArgument($receivedRequest)) { return $promise->getWrappedObject(); } @@ -189,9 +187,9 @@ function it_replace_full_url( $this->handleRequest($request, $next, $first); } - function it_throws_http_exception_on_no_location(RequestInterface $request, UriInterface $uri, ResponseInterface $responseRedirect) + public function it_throws_http_exception_on_no_location(RequestInterface $request, UriInterface $uri, ResponseInterface $responseRedirect) { - $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) { + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); } @@ -207,9 +205,9 @@ function it_throws_http_exception_on_no_location(RequestInterface $request, UriI $promise->shouldThrow('Http\Client\Exception\HttpException')->duringWait(); } - function it_throws_http_exception_on_invalid_location(RequestInterface $request, UriInterface $uri, ResponseInterface $responseRedirect) + public function it_throws_http_exception_on_invalid_location(RequestInterface $request, UriInterface $uri, ResponseInterface $responseRedirect) { - $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) { + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); } @@ -227,9 +225,9 @@ function it_throws_http_exception_on_invalid_location(RequestInterface $request, $promise->shouldThrow('Http\Client\Exception\HttpException')->duringWait(); } - function it_throw_multi_redirect_exception_on_300(RequestInterface $request, ResponseInterface $responseRedirect) + public function it_throw_multi_redirect_exception_on_300(RequestInterface $request, ResponseInterface $responseRedirect) { - $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) { + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); } @@ -243,9 +241,9 @@ function it_throw_multi_redirect_exception_on_300(RequestInterface $request, Res $promise->shouldThrow('Http\Client\Common\Exception\MultipleRedirectionException')->duringWait(); } - function it_throw_multi_redirect_exception_on_300_if_no_location(RequestInterface $request, ResponseInterface $responseRedirect) + public function it_throw_multi_redirect_exception_on_300_if_no_location(RequestInterface $request, ResponseInterface $responseRedirect) { - $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) { + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); } @@ -259,7 +257,7 @@ function it_throw_multi_redirect_exception_on_300_if_no_location(RequestInterfac $promise->shouldThrow('Http\Client\Common\Exception\MultipleRedirectionException')->duringWait(); } - function it_switch_method_for_302( + public function it_switch_method_for_302( UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, @@ -288,13 +286,13 @@ function it_switch_method_for_302( $modifiedRequest->getMethod()->willReturn('POST'); $modifiedRequest->withMethod('GET')->willReturn($modifiedRequest); - $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) { + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); } }; - $first = function (RequestInterface $receivedRequest) use($modifiedRequest, $promise) { + $first = function (RequestInterface $receivedRequest) use ($modifiedRequest, $promise) { if (Argument::is($modifiedRequest->getWrappedObject())->scoreArgument($receivedRequest)) { return $promise->getWrappedObject(); } @@ -306,7 +304,7 @@ function it_switch_method_for_302( $this->handleRequest($request, $next, $first); } - function it_clears_headers( + public function it_clears_headers( UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, @@ -338,13 +336,13 @@ function it_clears_headers( $modifiedRequest->withoutHeader('Cookie')->willReturn($modifiedRequest); $modifiedRequest->getUri()->willReturn($uriRedirect); - $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) { + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); } }; - $first = function (RequestInterface $receivedRequest) use($modifiedRequest, $promise) { + $first = function (RequestInterface $receivedRequest) use ($modifiedRequest, $promise) { if (Argument::is($modifiedRequest->getWrappedObject())->scoreArgument($receivedRequest)) { return $promise->getWrappedObject(); } @@ -356,12 +354,12 @@ function it_clears_headers( $this->handleRequest($request, $next, $first); } - function it_throws_circular_redirection_exception(UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, ResponseInterface $responseRedirect, RequestInterface $modifiedRequest) + public function it_throws_circular_redirection_exception(UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, ResponseInterface $responseRedirect, RequestInterface $modifiedRequest) { - $first = function() {}; + $first = function () {}; $this->beAnInstanceOf('spec\Http\Client\Common\Plugin\RedirectPluginStubCircular'); - $this->beConstructedWith(spl_object_hash((object)$first)); + $this->beConstructedWith(spl_object_hash((object) $first)); $request->getUri()->willReturn($uri); $uri->__toString()->willReturn('/original'); @@ -379,7 +377,7 @@ function it_throws_circular_redirection_exception(UriInterface $uri, UriInterfac $uriRedirect->__toString()->willReturn('/redirect'); $modifiedRequest->getMethod()->willReturn('GET'); - $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) { + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); } @@ -390,7 +388,7 @@ function it_throws_circular_redirection_exception(UriInterface $uri, UriInterfac $promise->shouldThrow('Http\Client\Common\Exception\CircularRedirectionException')->duringWait(); } - function it_redirects_http_to_https( + public function it_redirects_http_to_https( UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, @@ -417,13 +415,13 @@ function it_redirects_http_to_https( $modifiedRequest->getUri()->willReturn($uriRedirect); $modifiedRequest->getMethod()->willReturn('GET'); - $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) { + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); } }; - $first = function (RequestInterface $receivedRequest) use($modifiedRequest, $promise) { + $first = function (RequestInterface $receivedRequest) use ($modifiedRequest, $promise) { if (Argument::is($modifiedRequest->getWrappedObject())->scoreArgument($receivedRequest)) { return $promise->getWrappedObject(); } @@ -446,7 +444,7 @@ public function __construct(UriInterface $uri, $storedUrl, $status, array $confi $this->redirectStorage[$storedUrl] = [ 'uri' => $uri, - 'status' => $status + 'status' => $status, ]; } @@ -462,8 +460,8 @@ public function __construct($chainHash) { $this->circularDetection = [ $chainHash => [ - '/redirect' - ] + '/redirect', + ], ]; } } diff --git a/spec/Plugin/RequestMatcherPluginSpec.php b/spec/Plugin/RequestMatcherPluginSpec.php index 4fe9aea..bd4e7e4 100644 --- a/spec/Plugin/RequestMatcherPluginSpec.php +++ b/spec/Plugin/RequestMatcherPluginSpec.php @@ -11,22 +11,22 @@ class RequestMatcherPluginSpec extends ObjectBehavior { - function let(RequestMatcher $requestMatcher, Plugin $plugin) + public function let(RequestMatcher $requestMatcher, Plugin $plugin) { $this->beConstructedWith($requestMatcher, $plugin); } - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\Plugin\RequestMatcherPlugin'); } - function it_is_a_plugin() + public function it_is_a_plugin() { $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_matches_a_request_and_delegates_to_plugin( + public function it_matches_a_request_and_delegates_to_plugin( RequestInterface $request, RequestMatcher $requestMatcher, Plugin $plugin @@ -37,7 +37,7 @@ function it_matches_a_request_and_delegates_to_plugin( $this->handleRequest($request, function () {}, function () {}); } - function it_does_not_match_a_request( + public function it_does_not_match_a_request( RequestInterface $request, RequestMatcher $requestMatcher, Plugin $plugin, @@ -46,7 +46,7 @@ function it_does_not_match_a_request( $requestMatcher->matches($request)->willReturn(false); $plugin->handleRequest($request, Argument::type('callable'), Argument::type('callable'))->shouldNotBeCalled(); - $next = function (RequestInterface $request) use($promise) { + $next = function (RequestInterface $request) use ($promise) { return $promise->getWrappedObject(); }; diff --git a/spec/Plugin/RetryPluginSpec.php b/spec/Plugin/RetryPluginSpec.php index 37800ae..c439cad 100644 --- a/spec/Plugin/RetryPluginSpec.php +++ b/spec/Plugin/RetryPluginSpec.php @@ -12,19 +12,19 @@ class RetryPluginSpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\Plugin\RetryPlugin'); } - function it_is_a_plugin() + public function it_is_a_plugin() { $this->shouldImplement('Http\Client\Common\Plugin'); } - function it_returns_response(RequestInterface $request, ResponseInterface $response) + public function it_returns_response(RequestInterface $request, ResponseInterface $response) { - $next = function (RequestInterface $receivedRequest) use($request, $response) { + $next = function (RequestInterface $receivedRequest) use ($request, $response) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { return new HttpFulfilledPromise($response->getWrappedObject()); } @@ -33,20 +33,20 @@ function it_returns_response(RequestInterface $request, ResponseInterface $respo $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); } - function it_throws_exception_on_multiple_exceptions(RequestInterface $request) + public function it_throws_exception_on_multiple_exceptions(RequestInterface $request) { $exception1 = new Exception\NetworkException('Exception 1', $request->getWrappedObject()); $exception2 = new Exception\NetworkException('Exception 2', $request->getWrappedObject()); $count = 0; - $next = function (RequestInterface $receivedRequest) use($request, $exception1, $exception2, &$count) { - $count++; + $next = function (RequestInterface $receivedRequest) use ($request, $exception1, $exception2, &$count) { + ++$count; if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { - if ($count == 1) { + if (1 == $count) { return new HttpRejectedPromise($exception1); } - if ($count == 2) { + if (2 == $count) { return new HttpRejectedPromise($exception2); } } @@ -57,19 +57,19 @@ function it_throws_exception_on_multiple_exceptions(RequestInterface $request) $promise->shouldThrow($exception2)->duringWait(); } - function it_returns_response_on_second_try(RequestInterface $request, ResponseInterface $response) + public function it_returns_response_on_second_try(RequestInterface $request, ResponseInterface $response) { $exception = new Exception\NetworkException('Exception 1', $request->getWrappedObject()); $count = 0; - $next = function (RequestInterface $receivedRequest) use($request, $exception, $response, &$count) { - $count++; + $next = function (RequestInterface $receivedRequest) use ($request, $exception, $response, &$count) { + ++$count; if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { - if ($count == 1) { + if (1 == $count) { return new HttpRejectedPromise($exception); } - if ($count == 2) { + if (2 == $count) { return new HttpFulfilledPromise($response->getWrappedObject()); } } @@ -80,19 +80,19 @@ function it_returns_response_on_second_try(RequestInterface $request, ResponseIn $promise->wait()->shouldReturn($response); } - function it_does_not_keep_history_of_old_failure(RequestInterface $request, ResponseInterface $response) + public function it_does_not_keep_history_of_old_failure(RequestInterface $request, ResponseInterface $response) { $exception = new Exception\NetworkException('Exception 1', $request->getWrappedObject()); $count = 0; - $next = function (RequestInterface $receivedRequest) use($request, $exception, $response, &$count) { - $count++; + $next = function (RequestInterface $receivedRequest) use ($request, $exception, $response, &$count) { + ++$count; if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { - if ($count % 2 == 1) { + if (1 == $count % 2) { return new HttpRejectedPromise($exception); } - if ($count % 2 == 0) { + if (0 == $count % 2) { return new HttpFulfilledPromise($response->getWrappedObject()); } } @@ -102,7 +102,7 @@ function it_does_not_keep_history_of_old_failure(RequestInterface $request, Resp $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); } - function it_has_an_exponential_default_delay(RequestInterface $request, Exception\HttpException $exception) + public function it_has_an_exponential_default_delay(RequestInterface $request, Exception\HttpException $exception) { $this->defaultDelay($request, $exception, 0)->shouldBe(500000); $this->defaultDelay($request, $exception, 1)->shouldBe(1000000); diff --git a/spec/PluginClientFactorySpec.php b/spec/PluginClientFactorySpec.php index 1f8d9e8..fe7e076 100644 --- a/spec/PluginClientFactorySpec.php +++ b/spec/PluginClientFactorySpec.php @@ -7,19 +7,19 @@ class PluginClientFactorySpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\PluginClientFactory'); } - function it_returns_a_plugin_client(HttpClient $httpClient) + public function it_returns_a_plugin_client(HttpClient $httpClient) { $client = $this->createClient($httpClient); $client->shouldHaveType('Http\Client\Common\PluginClient'); } - function it_does_not_construct_plugin_client_with_client_name_option(HttpClient $httpClient) + public function it_does_not_construct_plugin_client_with_client_name_option(HttpClient $httpClient) { $this->createClient($httpClient, [], ['client_name' => 'Default']); } diff --git a/spec/PluginClientSpec.php b/spec/PluginClientSpec.php index 88406ae..50df636 100644 --- a/spec/PluginClientSpec.php +++ b/spec/PluginClientSpec.php @@ -4,7 +4,6 @@ use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; -use Http\Client\Common\FlexibleHttpClient; use Http\Client\Common\Plugin; use Http\Promise\Promise; use Prophecy\Argument; @@ -14,34 +13,34 @@ class PluginClientSpec extends ObjectBehavior { - function let(HttpClient $httpClient) + public function let(HttpClient $httpClient) { $this->beConstructedWith($httpClient); } - function it_is_initializable() + public function it_is_initializable() { $this->shouldHaveType('Http\Client\Common\PluginClient'); } - function it_is_an_http_client() + public function it_is_an_http_client() { $this->shouldImplement('Http\Client\HttpClient'); } - function it_is_an_http_async_client() + public function it_is_an_http_async_client() { $this->shouldImplement('Http\Client\HttpAsyncClient'); } - function it_sends_request_with_underlying_client(HttpClient $httpClient, RequestInterface $request, ResponseInterface $response) + public function it_sends_request_with_underlying_client(HttpClient $httpClient, RequestInterface $request, ResponseInterface $response) { $httpClient->sendRequest($request)->willReturn($response); $this->sendRequest($request)->shouldReturn($response); } - function it_sends_async_request_with_underlying_client(HttpAsyncClient $httpAsyncClient, RequestInterface $request, Promise $promise) + public function it_sends_async_request_with_underlying_client(HttpAsyncClient $httpAsyncClient, RequestInterface $request, Promise $promise) { $httpAsyncClient->sendAsyncRequest($request)->willReturn($promise); @@ -49,7 +48,7 @@ function it_sends_async_request_with_underlying_client(HttpAsyncClient $httpAsyn $this->sendAsyncRequest($request)->shouldReturn($promise); } - function it_sends_async_request_if_no_send_request(HttpAsyncClient $httpAsyncClient, RequestInterface $request, ResponseInterface $response, Promise $promise) + public function it_sends_async_request_if_no_send_request(HttpAsyncClient $httpAsyncClient, RequestInterface $request, ResponseInterface $response, Promise $promise) { $this->beConstructedWith($httpAsyncClient); $httpAsyncClient->sendAsyncRequest($request)->willReturn($promise); @@ -58,7 +57,7 @@ function it_sends_async_request_if_no_send_request(HttpAsyncClient $httpAsyncCli $this->sendRequest($request)->shouldReturn($response); } - function it_prefers_send_request($client, RequestInterface $request, ResponseInterface $response) + public function it_prefers_send_request($client, RequestInterface $request, ResponseInterface $response) { $client->implement('Http\Client\HttpClient'); $client->implement('Http\Client\HttpAsyncClient'); @@ -70,7 +69,7 @@ function it_prefers_send_request($client, RequestInterface $request, ResponseInt $this->sendRequest($request)->shouldReturn($response); } - function it_throws_loop_exception(HttpClient $httpClient, RequestInterface $request, Plugin $plugin) + public function it_throws_loop_exception(HttpClient $httpClient, RequestInterface $request, Plugin $plugin) { $plugin ->handleRequest( diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index fa32c56..ac7699c 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -62,7 +62,7 @@ public function addClient($client, RequestMatcher $requestMatcher) * * @return HttpClient|HttpAsyncClient */ - protected function chooseHttpClient(RequestInterface $request) + private function chooseHttpClient(RequestInterface $request) { foreach ($this->clients as $client) { if ($client['matcher']->matches($request)) { diff --git a/src/Plugin.php b/src/Plugin.php index 89a2a62..6248fc7 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -26,7 +26,7 @@ interface Plugin * @param callable $next Next middleware in the chain, the request is passed as the first argument * @param callable $first First middleware in the chain, used to to restart a request * - * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception (The same as HttpAsyncClient). + * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception (The same as HttpAsyncClient) */ public function handleRequest(RequestInterface $request, callable $next, callable $first); } diff --git a/src/Plugin/BaseUriPlugin.php b/src/Plugin/BaseUriPlugin.php index 2c2a775..669c308 100644 --- a/src/Plugin/BaseUriPlugin.php +++ b/src/Plugin/BaseUriPlugin.php @@ -24,7 +24,7 @@ final class BaseUriPlugin implements Plugin private $addPathPlugin = null; /** - * @param UriInterface $uri Has to contain a host name and cans have a path. + * @param UriInterface $uri Has to contain a host name and cans have a path * @param array $hostConfig Config for AddHostPlugin. @see AddHostPlugin::configureOptions */ public function __construct(UriInterface $uri, array $hostConfig = []) diff --git a/src/Plugin/ContentTypePlugin.php b/src/Plugin/ContentTypePlugin.php index 8ef1d62..d0bfa8d 100644 --- a/src/Plugin/ContentTypePlugin.php +++ b/src/Plugin/ContentTypePlugin.php @@ -22,19 +22,19 @@ final class ContentTypePlugin implements Plugin * true skip the content type detection * false detect the content type (default value) */ - protected $skipDetection; + private $skipDetection; /** * Determine the size stream limit for which the detection as to be skipped (default to 16Mb). * * @var int */ - protected $sizeLimit; + private $sizeLimit; /** * @param array $config { * - * @var bool $skip_detection True skip detection if stream size is bigger than $size_limit. + * @var bool $skip_detection true skip detection if stream size is bigger than $size_limit * @var int $size_limit size stream limit for which the detection as to be skipped. * } */ diff --git a/src/Plugin/ErrorPlugin.php b/src/Plugin/ErrorPlugin.php index bcc1c67..4fd7201 100644 --- a/src/Plugin/ErrorPlugin.php +++ b/src/Plugin/ErrorPlugin.php @@ -66,7 +66,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl * * @return ResponseInterface If status code is not in 4xx or 5xx return response */ - protected function transformResponseToException(RequestInterface $request, ResponseInterface $response) + private function transformResponseToException(RequestInterface $request, ResponseInterface $response) { if (!$this->onlyServerException && $response->getStatusCode() >= 400 && $response->getStatusCode() < 500) { throw new ClientErrorException($response->getReasonPhrase(), $request, $response); diff --git a/src/Plugin/RetryPlugin.php b/src/Plugin/RetryPlugin.php index 8446246..0a5b4e7 100644 --- a/src/Plugin/RetryPlugin.php +++ b/src/Plugin/RetryPlugin.php @@ -44,8 +44,8 @@ final class RetryPlugin implements Plugin /** * @param array $config { * - * @var int $retries Number of retries to attempt if an exception occurs before letting the exception bubble up. - * @var callable $decider A callback that gets a request and an exception to decide after a failure whether the request should be retried. + * @var int $retries Number of retries to attempt if an exception occurs before letting the exception bubble up + * @var callable $decider A callback that gets a request and an exception to decide after a failure whether the request should be retried * @var callable $delay A callback that gets a request, an exception and the number of retries and returns how many microseconds we should wait before trying again. * } */ From f966926c4cb3d49657903e73f02b09a54e80d68b Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 20 Nov 2018 16:40:45 +0100 Subject: [PATCH 006/123] Add rules to cleanup PHPDocs --- .php_cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.php_cs b/.php_cs index 7da3701..7039b1e 100644 --- a/.php_cs +++ b/.php_cs @@ -7,6 +7,8 @@ $config->setRules([ 'array_syntax' => [ 'syntax' => 'short', ], + 'no_empty_phpdoc' => true, + 'no_superfluous_phpdoc_tags' => true, ]); $finder = PhpCsFixer\Finder::create(); From 720a0cde7d1a608a70513c784cbc0728e8145a92 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 20 Nov 2018 16:43:55 +0100 Subject: [PATCH 007/123] Remove unuseful PHPDoc automatically --- src/BatchClient.php | 3 --- src/BatchResult.php | 14 -------------- src/EmulatedHttpAsyncClient.php | 3 --- src/EmulatedHttpClient.php | 3 --- src/Exception/BatchException.php | 3 --- src/HttpClientRouter.php | 3 --- src/HttpMethodsClient.php | 11 +---------- src/Plugin.php | 5 ++--- src/Plugin/AddHostPlugin.php | 6 +----- src/Plugin/AddPathPlugin.php | 3 --- src/Plugin/AuthenticationPlugin.php | 3 --- src/Plugin/CookiePlugin.php | 4 ---- src/Plugin/DecoderPlugin.php | 3 +-- src/Plugin/HistoryPlugin.php | 3 --- src/Plugin/RequestMatcherPlugin.php | 4 ---- src/Plugin/RetryPlugin.php | 4 +--- src/PluginClient.php | 2 -- src/PluginClientFactory.php | 2 -- 18 files changed, 6 insertions(+), 73 deletions(-) diff --git a/src/BatchClient.php b/src/BatchClient.php index 2a39904..7ddba4e 100644 --- a/src/BatchClient.php +++ b/src/BatchClient.php @@ -22,9 +22,6 @@ class BatchClient implements HttpClient */ private $client; - /** - * @param HttpClient $client - */ public function __construct(HttpClient $client) { $this->client = $client; diff --git a/src/BatchResult.php b/src/BatchResult.php index 710611d..3f8a2f0 100644 --- a/src/BatchResult.php +++ b/src/BatchResult.php @@ -58,8 +58,6 @@ public function getResponses() /** * Checks if there is a successful response for a request. * - * @param RequestInterface $request - * * @return bool */ public function isSuccessful(RequestInterface $request) @@ -70,8 +68,6 @@ public function isSuccessful(RequestInterface $request) /** * Returns the response for a successful request. * - * @param RequestInterface $request - * * @return ResponseInterface * * @throws \UnexpectedValueException If request was not part of the batch or failed @@ -88,9 +84,6 @@ public function getResponseFor(RequestInterface $request) /** * Adds a response in an immutable way. * - * @param RequestInterface $request - * @param ResponseInterface $response - * * @return BatchResult the new BatchResult with this request-response pair added to it */ public function addResponse(RequestInterface $request, ResponseInterface $response) @@ -130,8 +123,6 @@ public function getExceptions() /** * Checks if there is an exception for a request, meaning the request failed. * - * @param RequestInterface $request - * * @return bool */ public function isFailed(RequestInterface $request) @@ -142,8 +133,6 @@ public function isFailed(RequestInterface $request) /** * Returns the exception for a failed request. * - * @param RequestInterface $request - * * @return Exception * * @throws \UnexpectedValueException If request was not part of the batch or was successful @@ -160,9 +149,6 @@ public function getExceptionFor(RequestInterface $request) /** * Adds an exception in an immutable way. * - * @param RequestInterface $request - * @param Exception $exception - * * @return BatchResult the new BatchResult with this request-exception pair added to it */ public function addException(RequestInterface $request, Exception $exception) diff --git a/src/EmulatedHttpAsyncClient.php b/src/EmulatedHttpAsyncClient.php index 1b16316..64acd59 100644 --- a/src/EmulatedHttpAsyncClient.php +++ b/src/EmulatedHttpAsyncClient.php @@ -17,9 +17,6 @@ class EmulatedHttpAsyncClient implements HttpClient, HttpAsyncClient use HttpAsyncClientEmulator; use HttpClientDecorator; - /** - * @param HttpClient $httpClient - */ public function __construct(HttpClient $httpClient) { $this->httpClient = $httpClient; diff --git a/src/EmulatedHttpClient.php b/src/EmulatedHttpClient.php index 01046c8..3635e9f 100644 --- a/src/EmulatedHttpClient.php +++ b/src/EmulatedHttpClient.php @@ -17,9 +17,6 @@ class EmulatedHttpClient implements HttpClient, HttpAsyncClient use HttpAsyncClientDecorator; use HttpClientEmulator; - /** - * @param HttpAsyncClient $httpAsyncClient - */ public function __construct(HttpAsyncClient $httpAsyncClient) { $this->httpAsyncClient = $httpAsyncClient; diff --git a/src/Exception/BatchException.php b/src/Exception/BatchException.php index 66a9271..8620f9d 100644 --- a/src/Exception/BatchException.php +++ b/src/Exception/BatchException.php @@ -19,9 +19,6 @@ final class BatchException extends TransferException */ private $result; - /** - * @param BatchResult $result - */ public function __construct(BatchResult $result) { $this->result = $result; diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index ac7699c..00ca15f 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -45,7 +45,6 @@ public function sendAsyncRequest(RequestInterface $request) * Add a client to the router. * * @param HttpClient|HttpAsyncClient $client - * @param RequestMatcher $requestMatcher */ public function addClient($client, RequestMatcher $requestMatcher) { @@ -58,8 +57,6 @@ public function addClient($client, RequestMatcher $requestMatcher) /** * Choose an HTTP client given a specific request. * - * @param RequestInterface $request - * * @return HttpClient|HttpAsyncClient */ private function chooseHttpClient(RequestInterface $request) diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index 0ec1e14..a1ef2c8 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -50,7 +50,6 @@ public function __construct(HttpClient $httpClient, RequestFactory $requestFacto * Sends a GET request. * * @param string|UriInterface $uri - * @param array $headers * * @throws Exception * @@ -65,7 +64,6 @@ public function get($uri, array $headers = []) * Sends an HEAD request. * * @param string|UriInterface $uri - * @param array $headers * * @throws Exception * @@ -80,7 +78,6 @@ public function head($uri, array $headers = []) * Sends a TRACE request. * * @param string|UriInterface $uri - * @param array $headers * * @throws Exception * @@ -95,7 +92,6 @@ public function trace($uri, array $headers = []) * Sends a POST request. * * @param string|UriInterface $uri - * @param array $headers * @param string|StreamInterface|null $body * * @throws Exception @@ -111,7 +107,6 @@ public function post($uri, array $headers = [], $body = null) * Sends a PUT request. * * @param string|UriInterface $uri - * @param array $headers * @param string|StreamInterface|null $body * * @throws Exception @@ -127,7 +122,6 @@ public function put($uri, array $headers = [], $body = null) * Sends a PATCH request. * * @param string|UriInterface $uri - * @param array $headers * @param string|StreamInterface|null $body * * @throws Exception @@ -143,7 +137,6 @@ public function patch($uri, array $headers = [], $body = null) * Sends a DELETE request. * * @param string|UriInterface $uri - * @param array $headers * @param string|StreamInterface|null $body * * @throws Exception @@ -159,7 +152,6 @@ public function delete($uri, array $headers = [], $body = null) * Sends an OPTIONS request. * * @param string|UriInterface $uri - * @param array $headers * @param string|StreamInterface|null $body * * @throws Exception @@ -174,9 +166,8 @@ public function options($uri, array $headers = [], $body = null) /** * Sends a request with any HTTP method. * - * @param string $method HTTP method to use + * @param string $method HTTP method to use * @param string|UriInterface $uri - * @param array $headers * @param string|StreamInterface|null $body * * @throws Exception diff --git a/src/Plugin.php b/src/Plugin.php index 6248fc7..bfb2668 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -22,9 +22,8 @@ interface Plugin * * @see http://docs.php-http.org/en/latest/plugins/build-your-own.html * - * @param RequestInterface $request - * @param callable $next Next middleware in the chain, the request is passed as the first argument - * @param callable $first First middleware in the chain, used to to restart a request + * @param callable $next Next middleware in the chain, the request is passed as the first argument + * @param callable $first First middleware in the chain, used to to restart a request * * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception (The same as HttpAsyncClient) */ diff --git a/src/Plugin/AddHostPlugin.php b/src/Plugin/AddHostPlugin.php index 29ab8ae..050feb1 100644 --- a/src/Plugin/AddHostPlugin.php +++ b/src/Plugin/AddHostPlugin.php @@ -25,8 +25,7 @@ final class AddHostPlugin implements Plugin private $replace; /** - * @param UriInterface $host - * @param array $config { + * @param array $config { * * @var bool $replace True will replace all hosts, false will only add host when none is specified. * } @@ -64,9 +63,6 @@ public function handleRequest(RequestInterface $request, callable $next, callabl return $next($request); } - /** - * @param OptionsResolver $resolver - */ private function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Plugin/AddPathPlugin.php b/src/Plugin/AddPathPlugin.php index 93ded1c..1675088 100644 --- a/src/Plugin/AddPathPlugin.php +++ b/src/Plugin/AddPathPlugin.php @@ -25,9 +25,6 @@ final class AddPathPlugin implements Plugin */ private $alteredRequests = []; - /** - * @param UriInterface $uri - */ public function __construct(UriInterface $uri) { if ('' === $uri->getPath()) { diff --git a/src/Plugin/AuthenticationPlugin.php b/src/Plugin/AuthenticationPlugin.php index 194712f..ff33f9b 100644 --- a/src/Plugin/AuthenticationPlugin.php +++ b/src/Plugin/AuthenticationPlugin.php @@ -18,9 +18,6 @@ final class AuthenticationPlugin implements Plugin */ private $authentication; - /** - * @param Authentication $authentication - */ public function __construct(Authentication $authentication) { $this->authentication = $authentication; diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index 523628f..3ab5388 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -25,9 +25,6 @@ final class CookiePlugin implements Plugin */ private $cookieJar; - /** - * @param CookieJar $cookieJar - */ public function __construct(CookieJar $cookieJar) { $this->cookieJar = $cookieJar; @@ -91,7 +88,6 @@ public function handleRequest(RequestInterface $request, callable $next, callabl /** * Creates a cookie from a string. * - * @param RequestInterface $request * @param $setCookie * * @return Cookie|null diff --git a/src/Plugin/DecoderPlugin.php b/src/Plugin/DecoderPlugin.php index 0239d40..3a28f5b 100644 --- a/src/Plugin/DecoderPlugin.php +++ b/src/Plugin/DecoderPlugin.php @@ -120,8 +120,7 @@ private function decodeOnEncodingHeader($headerName, ResponseInterface $response /** * Decorate a stream given an encoding. * - * @param string $encoding - * @param StreamInterface $stream + * @param string $encoding * * @return StreamInterface|false A new stream interface or false if encoding is not supported */ diff --git a/src/Plugin/HistoryPlugin.php b/src/Plugin/HistoryPlugin.php index 5abddbd..0c59a2a 100644 --- a/src/Plugin/HistoryPlugin.php +++ b/src/Plugin/HistoryPlugin.php @@ -21,9 +21,6 @@ final class HistoryPlugin implements Plugin */ private $journal; - /** - * @param Journal $journal - */ public function __construct(Journal $journal) { $this->journal = $journal; diff --git a/src/Plugin/RequestMatcherPlugin.php b/src/Plugin/RequestMatcherPlugin.php index 5f72b02..dd97b3c 100644 --- a/src/Plugin/RequestMatcherPlugin.php +++ b/src/Plugin/RequestMatcherPlugin.php @@ -23,10 +23,6 @@ final class RequestMatcherPlugin implements Plugin */ private $delegatedPlugin; - /** - * @param RequestMatcher $requestMatcher - * @param Plugin $delegatedPlugin - */ public function __construct(RequestMatcher $requestMatcher, Plugin $delegatedPlugin) { $this->requestMatcher = $requestMatcher; diff --git a/src/Plugin/RetryPlugin.php b/src/Plugin/RetryPlugin.php index 0a5b4e7..96cfbb2 100644 --- a/src/Plugin/RetryPlugin.php +++ b/src/Plugin/RetryPlugin.php @@ -109,9 +109,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl } /** - * @param RequestInterface $request - * @param Exception $e - * @param int $retries The number of retries we made before. First time this get called it will be 0. + * @param int $retries The number of retries we made before. First time this get called it will be 0. * * @return int */ diff --git a/src/PluginClient.php b/src/PluginClient.php index c3a5641..6881c93 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -102,8 +102,6 @@ public function sendAsyncRequest(RequestInterface $request) /** * Configure the plugin client. * - * @param array $options - * * @return array */ private function configure(array $options = []) diff --git a/src/PluginClientFactory.php b/src/PluginClientFactory.php index bd4c08f..a262f80 100644 --- a/src/PluginClientFactory.php +++ b/src/PluginClientFactory.php @@ -26,8 +26,6 @@ final class PluginClientFactory * application execution. * * @internal - * - * @param callable $factory */ public static function setFactory(callable $factory) { From 637d01c4bac54a586ba4a16a507f7b9872aa5137 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 20 Nov 2018 16:52:59 +0100 Subject: [PATCH 008/123] Migrate to Travis CI job sequence --- .travis.yml | 96 +++++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0c9a55f..97908bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,62 +2,64 @@ language: php sudo: false cache: - directories: - - $HOME/.composer/cache/files + directories: + - $HOME/.composer/cache/files env: - global: - - TEST_COMMAND="composer test" + global: + - TEST_COMMAND="composer test" branches: - except: - - /^analysis-.*$/ + except: + - /^analysis-.*$/ + +php: +- 7.0 +- 7.1 +- 7.2 matrix: - fast_finish: true - include: - - php: 7.0 - env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" - - # Test the latest stable release - - php: 7.0 - - php: 7.1 - - php: 7.2 - - php: 7.2 - env: COVERAGE=true TEST_COMMAND="composer test-ci" DEPENDENCIES="leanphp/phpspec-code-coverage" - - # Test LTS versions - - php: 7.1 - env: DEPENDENCIES="dunglas/symfony-lock:^2" - - php: 7.1 - env: DEPENDENCIES="dunglas/symfony-lock:^3" - - php: 7.1 - env: DEPENDENCIES="dunglas/symfony-lock:^4" STABILITY="rc" - - # Latest dev release - - php: 7.1 - env: STABILITY="dev" - - allow_failures: - - php: 7.3 - sudo: required - - env: STABILITY="dev" + fast_finish: true + allow_failures: + - php: 7.3 + - env: + STABILITY: "dev" + +jobs: + include: + - php: 7.0 + env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" + - php: 7.2 + env: COVERAGE=true DEPENDENCIES="leanphp/phpspec-code-coverage" + script: + - composer test-ci + after_success: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml --revision=$TRAVIS_COMMIT + # Test LTS versions + - php: 7.1 + env: DEPENDENCIES="dunglas/symfony-lock:^2" + - php: 7.1 + env: DEPENDENCIES="dunglas/symfony-lock:^3" + - php: 7.1 + env: DEPENDENCIES="dunglas/symfony-lock:^4" STABILITY="rc" + + # Latest dev release + - php: 7.3 + sudo: required + - env: STABILITY="dev" before_install: - - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi - - if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi; - - if ! [ -z "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi; +- if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi +- if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi; +- if ! [ -z "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi; install: - - cat composer.json - # To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355 - - if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi - - composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction +- cat composer.json +# To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355 +- if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi +- composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction script: - - composer validate --strict --no-check-lock - - $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 +- composer validate --strict --no-check-lock +- composer test From ce5a98c779c218c26c3afe9aa34729ac50ccac99 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 20 Nov 2018 17:01:01 +0100 Subject: [PATCH 009/123] Remove CS-fixer under 7.3 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 97908bf..796c813 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,6 @@ matrix: fast_finish: true allow_failures: - php: 7.3 - - env: - STABILITY: "dev" jobs: include: @@ -47,6 +45,8 @@ jobs: # Latest dev release - php: 7.3 sudo: required + before_install: + - composer remove --dev friendsofphp/php-cs-fixer - env: STABILITY="dev" before_install: From 1cf0f9efb77e943d9af75095c9ebd34d37acbd47 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 20 Nov 2018 17:36:31 +0100 Subject: [PATCH 010/123] Remove StyleCI config --- .styleci.yml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .styleci.yml diff --git a/.styleci.yml b/.styleci.yml deleted file mode 100644 index 5328b61..0000000 --- a/.styleci.yml +++ /dev/null @@ -1,14 +0,0 @@ -preset: symfony - -finder: - exclude: - - "spec" - path: - - "src" - - "tests" - -enabled: - - short_array_syntax - -disabled: - - phpdoc_annotation_without_dot # This is still buggy: https://github.com/symfony/symfony/pull/19198 From 543b2a2d8bee5499231cc48c5e081337e6c90e6f Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 22 Nov 2018 10:27:30 +0100 Subject: [PATCH 011/123] [2.0] Add types (#117) * Add types where possible * Add spec to autoload-dev * Fix signatures * Use ::class anywhere * Add `: Promise` return type to Plugin::handleRequest --- CHANGELOG.md | 4 ++ composer.json | 5 ++ spec/BatchClientSpec.php | 12 ++-- spec/BatchResultSpec.php | 7 +- spec/EmulatedHttpAsyncClientSpec.php | 17 +++-- spec/EmulatedHttpClientSpec.php | 11 +-- spec/Exception/BatchExceptionSpec.php | 10 +-- spec/FlexibleHttpClientSpec.php | 15 ++-- .../LeastUsedClientPoolSpec.php | 25 ++++--- spec/HttpClientPool/RandomClientPoolSpec.php | 25 ++++--- .../RoundRobinClientPoolSpec.php | 25 ++++--- spec/HttpClientPoolItemSpec.php | 15 ++-- spec/HttpClientRouterSpec.php | 12 ++-- spec/HttpMethodsClientSpec.php | 61 ++++++++++------ spec/Plugin/AddHostPluginSpec.php | 12 ++-- spec/Plugin/AddPathPluginSpec.php | 12 ++-- spec/Plugin/AuthenticationPluginSpec.php | 6 +- spec/Plugin/BaseUriPluginSpec.php | 12 ++-- spec/Plugin/ContentLengthPluginSpec.php | 10 +-- spec/Plugin/ContentTypePluginSpec.php | 24 ++++--- spec/Plugin/CookiePluginSpec.php | 71 +++++-------------- spec/Plugin/DecoderPluginSpec.php | 12 ++-- spec/Plugin/ErrorPluginSpec.php | 21 +++--- spec/Plugin/HeaderAppendPluginSpec.php | 10 +-- spec/Plugin/HeaderDefaultsPluginSpec.php | 10 +-- spec/Plugin/HeaderRemovePluginSpec.php | 10 +-- spec/Plugin/HeaderSetPluginSpec.php | 10 +-- spec/Plugin/HistoryPluginSpec.php | 3 +- spec/Plugin/PluginStub.php | 25 +++++++ spec/Plugin/QueryDefaultsPluginSpec.php | 14 ++-- spec/Plugin/RedirectPluginSpec.php | 45 ++++++------ spec/Plugin/RequestMatcherPluginSpec.php | 7 +- spec/Plugin/RetryPluginSpec.php | 16 +++-- spec/PluginClientFactorySpec.php | 6 +- spec/PluginClientSpec.php | 14 ++-- src/BatchClient.php | 2 +- src/BatchResult.php | 30 +++----- src/Deferred.php | 4 +- src/HttpClientPool.php | 2 +- src/HttpClientPool/LeastUsedClientPool.php | 2 +- src/HttpClientPool/RandomClientPool.php | 2 +- src/HttpClientPool/RoundRobinClientPool.php | 3 +- src/HttpMethodsClient.php | 36 +++------- src/Plugin.php | 2 +- src/Plugin/AddHostPlugin.php | 3 +- src/Plugin/AddPathPlugin.php | 3 +- src/Plugin/AuthenticationPlugin.php | 3 +- src/Plugin/BaseUriPlugin.php | 3 +- src/Plugin/ContentLengthPlugin.php | 3 +- src/Plugin/ContentTypePlugin.php | 7 +- src/Plugin/CookiePlugin.php | 5 +- src/Plugin/DecoderPlugin.php | 3 +- src/Plugin/ErrorPlugin.php | 5 +- src/Plugin/HeaderAppendPlugin.php | 5 +- src/Plugin/HeaderDefaultsPlugin.php | 3 +- src/Plugin/HeaderRemovePlugin.php | 3 +- src/Plugin/HeaderSetPlugin.php | 5 +- src/Plugin/HistoryPlugin.php | 3 +- src/Plugin/QueryDefaultsPlugin.php | 3 +- src/Plugin/RedirectPlugin.php | 7 +- src/Plugin/RequestMatcherPlugin.php | 3 +- src/Plugin/RetryPlugin.php | 3 +- src/PluginClient.php | 8 +-- src/PluginClientFactory.php | 4 +- 64 files changed, 415 insertions(+), 344 deletions(-) create mode 100644 spec/Plugin/PluginStub.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c268e8..c6a01d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 2.0 (unreleased) +### Changed +- Abstract method `HttpClientPool::chooseHttpClient()` has now an explicit return type (`Http\Client\Common\HttpClientPoolItem`) +- Interface method `Plugin::handleRequest(...)` has now an explicit return type (`Http\Promise\Promise`) + ### Removed - Deprecated option `debug_plugins` has been removed from `PluginClient` diff --git a/composer.json b/composer.json index 882758b..0b32bc2 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,11 @@ "Http\\Client\\Common\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "spec\\Http\\Client\\Common\\": "spec/" + } + }, "scripts": { "cs-check": "vendor/bin/php-cs-fixer fix --dry-run", "cs-fix": "vendor/bin/php-cs-fixer fix", diff --git a/spec/BatchClientSpec.php b/spec/BatchClientSpec.php index 8b7e96d..86c6c8b 100644 --- a/spec/BatchClientSpec.php +++ b/spec/BatchClientSpec.php @@ -6,12 +6,16 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\BatchClient; +use Http\Client\Common\BatchResult; +use Http\Client\Exception\HttpException; +use Http\Client\Common\Exception\BatchException; class BatchClientSpec extends ObjectBehavior { public function let(HttpClient $client) { - $this->beAnInstanceOf('Http\Client\Common\BatchClient', [$client]); + $this->beAnInstanceOf(BatchClient::class, [$client]); } public function it_send_multiple_request_using_send_request(HttpClient $client, RequestInterface $request1, RequestInterface $request2, ResponseInterface $response1, ResponseInterface $response2) @@ -19,14 +23,14 @@ public function it_send_multiple_request_using_send_request(HttpClient $client, $client->sendRequest($request1)->willReturn($response1); $client->sendRequest($request2)->willReturn($response2); - $this->sendRequests([$request1, $request2])->shouldReturnAnInstanceOf('Http\Client\Common\BatchResult'); + $this->sendRequests([$request1, $request2])->shouldReturnAnInstanceOf(BatchResult::class); } public function it_throw_batch_exception_if_one_or_more_request_failed(HttpClient $client, RequestInterface $request1, RequestInterface $request2, ResponseInterface $response) { $client->sendRequest($request1)->willReturn($response); - $client->sendRequest($request2)->willThrow('Http\Client\Exception\HttpException'); + $client->sendRequest($request2)->willThrow(HttpException::class); - $this->shouldThrow('Http\Client\Common\Exception\BatchException')->duringSendRequests([$request1, $request2]); + $this->shouldThrow(BatchException::class)->duringSendRequests([$request1, $request2]); } } diff --git a/spec/BatchResultSpec.php b/spec/BatchResultSpec.php index aa3bb14..90a80a5 100644 --- a/spec/BatchResultSpec.php +++ b/spec/BatchResultSpec.php @@ -6,12 +6,13 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\BatchResult; class BatchResultSpec extends ObjectBehavior { public function it_is_initializable() { - $this->beAnInstanceOf('Http\Client\Common\BatchResult'); + $this->beAnInstanceOf(BatchResult::class); } public function it_is_immutable(RequestInterface $request, ResponseInterface $response) @@ -19,7 +20,7 @@ public function it_is_immutable(RequestInterface $request, ResponseInterface $re $new = $this->addResponse($request, $response); $this->getResponses()->shouldReturn([]); - $new->shouldHaveType('Http\Client\Common\BatchResult'); + $new->shouldHaveType(BatchResult::class); $new->getResponses()->shouldReturn([$response]); } @@ -37,7 +38,7 @@ public function it_has_a_response_for_a_request(RequestInterface $request, Respo { $new = $this->addResponse($request, $response); - $this->shouldThrow('UnexpectedValueException')->duringGetResponseFor($request); + $this->shouldThrow(\UnexpectedValueException::class)->duringGetResponseFor($request); $this->isSuccessful($request)->shouldReturn(false); $new->getResponseFor($request)->shouldReturn($response); $new->isSuccessful($request)->shouldReturn(true); diff --git a/spec/EmulatedHttpAsyncClientSpec.php b/spec/EmulatedHttpAsyncClientSpec.php index ca8a4d1..226008b 100644 --- a/spec/EmulatedHttpAsyncClientSpec.php +++ b/spec/EmulatedHttpAsyncClientSpec.php @@ -6,6 +6,11 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\EmulatedHttpAsyncClient; +use Http\Client\HttpAsyncClient; +use Http\Client\Promise\HttpFulfilledPromise; +use Http\Client\Exception\TransferException; +use Http\Client\Promise\HttpRejectedPromise; class EmulatedHttpAsyncClientSpec extends ObjectBehavior { @@ -16,17 +21,17 @@ public function let(HttpClient $httpClient) public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\EmulatedHttpAsyncClient'); + $this->shouldHaveType(EmulatedHttpAsyncClient::class); } public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } public function it_is_an_async_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } public function it_emulates_a_successful_request( @@ -36,14 +41,14 @@ public function it_emulates_a_successful_request( ) { $httpClient->sendRequest($request)->willReturn($response); - $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); } public function it_emulates_a_failed_request(HttpClient $httpClient, RequestInterface $request) { - $httpClient->sendRequest($request)->willThrow('Http\Client\Exception\TransferException'); + $httpClient->sendRequest($request)->willThrow(TransferException::class); - $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); + $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf(HttpRejectedPromise::class); } public function it_decorates_the_underlying_client( diff --git a/spec/EmulatedHttpClientSpec.php b/spec/EmulatedHttpClientSpec.php index cf6e076..adf206f 100644 --- a/spec/EmulatedHttpClientSpec.php +++ b/spec/EmulatedHttpClientSpec.php @@ -4,10 +4,13 @@ use Http\Client\Exception\TransferException; use Http\Client\HttpAsyncClient; +use Http\Client\HttpClient; use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\EmulatedHttpClient; +use Http\Client\Exception; class EmulatedHttpClientSpec extends ObjectBehavior { @@ -18,17 +21,17 @@ public function let(HttpAsyncClient $httpAsyncClient) public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\EmulatedHttpClient'); + $this->shouldHaveType(EmulatedHttpClient::class); } public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } public function it_is_an_async_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } public function it_emulates_a_successful_request( @@ -54,7 +57,7 @@ public function it_emulates_a_failed_request(HttpAsyncClient $httpAsyncClient, R $httpAsyncClient->sendAsyncRequest($request)->willReturn($promise); - $this->shouldThrow('Http\Client\Exception')->duringSendRequest($request); + $this->shouldThrow(Exception::class)->duringSendRequest($request); } public function it_decorates_the_underlying_client( diff --git a/spec/Exception/BatchExceptionSpec.php b/spec/Exception/BatchExceptionSpec.php index 0a32607..0205843 100644 --- a/spec/Exception/BatchExceptionSpec.php +++ b/spec/Exception/BatchExceptionSpec.php @@ -3,7 +3,9 @@ namespace spec\Http\Client\Common\Exception; use Http\Client\Common\BatchResult; +use Http\Client\Exception; use PhpSpec\ObjectBehavior; +use Http\Client\Common\Exception\BatchException; class BatchExceptionSpec extends ObjectBehavior { @@ -15,21 +17,21 @@ public function let() public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Exception\BatchException'); + $this->shouldHaveType(BatchException::class); } public function it_is_a_runtime_exception() { - $this->shouldHaveType('RuntimeException'); + $this->shouldHaveType(\RuntimeException::class); } public function it_is_an_exception() { - $this->shouldImplement('Http\Client\Exception'); + $this->shouldImplement(Exception::class); } public function it_has_a_batch_result() { - $this->getResult()->shouldHaveType('Http\Client\Common\BatchResult'); + $this->getResult()->shouldHaveType(BatchResult::class); } } diff --git a/spec/FlexibleHttpClientSpec.php b/spec/FlexibleHttpClientSpec.php index 769b4c3..b63aa3d 100644 --- a/spec/FlexibleHttpClientSpec.php +++ b/spec/FlexibleHttpClientSpec.php @@ -8,6 +8,7 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\FlexibleHttpClient; class FlexibleHttpClientSpec extends ObjectBehavior { @@ -18,24 +19,24 @@ public function let(HttpClient $httpClient) public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\FlexibleHttpClient'); + $this->shouldHaveType(FlexibleHttpClient::class); } public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } public function it_is_an_async_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } public function it_throw_exception_if_invalid_client() { $this->beConstructedWith(null); - $this->shouldThrow('\LogicException')->duringInstantiation(); + $this->shouldThrow(\LogicException::class)->duringInstantiation(); } public function it_emulates_an_async_client( @@ -53,7 +54,7 @@ public function it_emulates_an_async_client( $this->sendRequest($syncRequest)->shouldReturn($syncResponse); $promise = $this->sendAsyncRequest($asyncRequest); - $promise->shouldHaveType('Http\Promise\Promise'); + $promise->shouldHaveType(Promise::class); $promise->wait()->shouldReturn($asyncResponse); } @@ -77,8 +78,8 @@ public function it_emulates_a_client( public function it_does_not_emulate_a_client($client, RequestInterface $syncRequest, RequestInterface $asyncRequest) { - $client->implement('Http\Client\HttpClient'); - $client->implement('Http\Client\HttpAsyncClient'); + $client->implement(HttpClient::class); + $client->implement(HttpAsyncClient::class); $client->sendRequest($syncRequest)->shouldBeCalled(); $client->sendRequest($asyncRequest)->shouldNotBeCalled(); diff --git a/spec/HttpClientPool/LeastUsedClientPoolSpec.php b/spec/HttpClientPool/LeastUsedClientPoolSpec.php index 8642c31..9c02e47 100644 --- a/spec/HttpClientPool/LeastUsedClientPoolSpec.php +++ b/spec/HttpClientPool/LeastUsedClientPoolSpec.php @@ -10,28 +10,31 @@ use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Http\Client\Common\HttpClientPool\LeastUsedClientPool; +use Http\Client\Common\Exception\HttpClientNotFoundException; +use Http\Client\Exception\HttpException; class LeastUsedClientPoolSpec extends ObjectBehavior { public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\HttpClientPool\LeastUsedClientPool'); + $this->shouldHaveType(LeastUsedClientPool::class); } public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } public function it_is_an_async_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } public function it_throw_exception_with_no_client(RequestInterface $request) { - $this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendRequest($request); - $this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendAsyncRequest($request); + $this->shouldThrow(HttpClientNotFoundException::class)->duringSendRequest($request); + $this->shouldThrow(HttpClientNotFoundException::class)->duringSendAsyncRequest($request); } public function it_sends_request(HttpClient $httpClient, RequestInterface $request, ResponseInterface $response) @@ -54,19 +57,19 @@ public function it_sends_async_request(HttpAsyncClient $httpAsyncClient, Request public function it_throw_exception_if_no_more_enable_client(HttpClient $client, RequestInterface $request) { $this->addHttpClient($client); - $client->sendRequest($request)->willThrow('Http\Client\Exception\HttpException'); + $client->sendRequest($request)->willThrow(HttpException::class); - $this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request); - $this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendRequest($request); + $this->shouldThrow(HttpException::class)->duringSendRequest($request); + $this->shouldThrow(HttpClientNotFoundException::class)->duringSendRequest($request); } public function it_reenable_client(HttpClient $client, RequestInterface $request) { $this->addHttpClient(new HttpClientPoolItem($client->getWrappedObject(), 0)); - $client->sendRequest($request)->willThrow('Http\Client\Exception\HttpException'); + $client->sendRequest($request)->willThrow(HttpException::class); - $this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request); - $this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request); + $this->shouldThrow(HttpException::class)->duringSendRequest($request); + $this->shouldThrow(HttpException::class)->duringSendRequest($request); } public function it_uses_the_lowest_request_client(HttpClientPoolItem $client1, HttpClientPoolItem $client2, RequestInterface $request, ResponseInterface $response) diff --git a/spec/HttpClientPool/RandomClientPoolSpec.php b/spec/HttpClientPool/RandomClientPoolSpec.php index 4054d82..fb0e5ad 100644 --- a/spec/HttpClientPool/RandomClientPoolSpec.php +++ b/spec/HttpClientPool/RandomClientPoolSpec.php @@ -10,28 +10,31 @@ use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Http\Client\Common\HttpClientPool\RandomClientPool; +use Http\Client\Common\Exception\HttpClientNotFoundException; +use Http\Client\Exception\HttpException; class RandomClientPoolSpec extends ObjectBehavior { public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\HttpClientPool\RandomClientPool'); + $this->shouldHaveType(RandomClientPool::class); } public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } public function it_is_an_async_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } public function it_throw_exception_with_no_client(RequestInterface $request) { - $this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendRequest($request); - $this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendAsyncRequest($request); + $this->shouldThrow(HttpClientNotFoundException::class)->duringSendRequest($request); + $this->shouldThrow(HttpClientNotFoundException::class)->duringSendAsyncRequest($request); } public function it_sends_request(HttpClient $httpClient, RequestInterface $request, ResponseInterface $response) @@ -54,18 +57,18 @@ public function it_sends_async_request(HttpAsyncClient $httpAsyncClient, Request public function it_throw_exception_if_no_more_enable_client(HttpClient $client, RequestInterface $request) { $this->addHttpClient($client); - $client->sendRequest($request)->willThrow('Http\Client\Exception\HttpException'); + $client->sendRequest($request)->willThrow(HttpException::class); - $this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request); - $this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendRequest($request); + $this->shouldThrow(HttpException::class)->duringSendRequest($request); + $this->shouldThrow(HttpClientNotFoundException::class)->duringSendRequest($request); } public function it_reenable_client(HttpClient $client, RequestInterface $request) { $this->addHttpClient(new HttpClientPoolItem($client->getWrappedObject(), 0)); - $client->sendRequest($request)->willThrow('Http\Client\Exception\HttpException'); + $client->sendRequest($request)->willThrow(HttpException::class); - $this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request); - $this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request); + $this->shouldThrow(HttpException::class)->duringSendRequest($request); + $this->shouldThrow(HttpException::class)->duringSendRequest($request); } } diff --git a/spec/HttpClientPool/RoundRobinClientPoolSpec.php b/spec/HttpClientPool/RoundRobinClientPoolSpec.php index 48c2d01..925f548 100644 --- a/spec/HttpClientPool/RoundRobinClientPoolSpec.php +++ b/spec/HttpClientPool/RoundRobinClientPoolSpec.php @@ -10,28 +10,31 @@ use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Http\Client\Common\HttpClientPool\RoundRobinClientPool; +use Http\Client\Common\Exception\HttpClientNotFoundException; +use Http\Client\Exception\HttpException; class RoundRobinClientPoolSpec extends ObjectBehavior { public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\HttpClientPool\RoundRobinClientPool'); + $this->shouldHaveType(RoundRobinClientPool::class); } public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } public function it_is_an_async_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } public function it_throw_exception_with_no_client(RequestInterface $request) { - $this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendRequest($request); - $this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendAsyncRequest($request); + $this->shouldThrow(HttpClientNotFoundException::class)->duringSendRequest($request); + $this->shouldThrow(HttpClientNotFoundException::class)->duringSendAsyncRequest($request); } public function it_sends_request(HttpClient $httpClient, RequestInterface $request, ResponseInterface $response) @@ -54,19 +57,19 @@ public function it_sends_async_request(HttpAsyncClient $httpAsyncClient, Request public function it_throw_exception_if_no_more_enable_client(HttpClient $client, RequestInterface $request) { $this->addHttpClient($client); - $client->sendRequest($request)->willThrow('Http\Client\Exception\HttpException'); + $client->sendRequest($request)->willThrow(HttpException::class); - $this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request); - $this->shouldThrow('Http\Client\Common\Exception\HttpClientNotFoundException')->duringSendRequest($request); + $this->shouldThrow(HttpException::class)->duringSendRequest($request); + $this->shouldThrow(HttpClientNotFoundException::class)->duringSendRequest($request); } public function it_reenable_client(HttpClient $client, RequestInterface $request) { $this->addHttpClient(new HttpClientPoolItem($client->getWrappedObject(), 0)); - $client->sendRequest($request)->willThrow('Http\Client\Exception\HttpException'); + $client->sendRequest($request)->willThrow(HttpException::class); - $this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request); - $this->shouldThrow('Http\Client\Exception\HttpException')->duringSendRequest($request); + $this->shouldThrow(HttpException::class)->duringSendRequest($request); + $this->shouldThrow(HttpException::class)->duringSendRequest($request); } public function it_round_between_clients(HttpClient $client1, HttpClient $client2, RequestInterface $request, ResponseInterface $response) diff --git a/spec/HttpClientPoolItemSpec.php b/spec/HttpClientPoolItemSpec.php index cba68d8..f44351b 100644 --- a/spec/HttpClientPoolItemSpec.php +++ b/spec/HttpClientPoolItemSpec.php @@ -12,6 +12,7 @@ use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Http\Client\Exception\RequestException; class HttpClientPoolItemSpec extends ObjectBehavior { @@ -22,12 +23,12 @@ public function let(HttpClient $httpClient) public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } public function it_is_an_async_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } public function it_sends_request(HttpClient $httpClient, RequestInterface $request, ResponseInterface $response) @@ -53,7 +54,7 @@ public function it_disable_himself_on_send_request(HttpClient $httpClient, Reque $httpClient->sendRequest($request)->willThrow($exception); $this->shouldThrow($exception)->duringSendRequest($request); $this->isDisabled()->shouldReturn(true); - $this->shouldThrow('Http\Client\Exception\RequestException')->duringSendRequest($request); + $this->shouldThrow(RequestException::class)->duringSendRequest($request); } public function it_disable_himself_on_send_async_request(HttpAsyncClient $httpAsyncClient, RequestInterface $request) @@ -63,9 +64,9 @@ public function it_disable_himself_on_send_async_request(HttpAsyncClient $httpAs $promise = new HttpRejectedPromise(new TransferException()); $httpAsyncClient->sendAsyncRequest($request)->willReturn($promise); - $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); + $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf(HttpRejectedPromise::class); $this->isDisabled()->shouldReturn(true); - $this->shouldThrow('Http\Client\Exception\RequestException')->duringSendAsyncRequest($request); + $this->shouldThrow(RequestException::class)->duringSendAsyncRequest($request); } public function it_reactivate_himself_on_send_request(HttpClient $httpClient, RequestInterface $request) @@ -87,9 +88,9 @@ public function it_reactivate_himself_on_send_async_request(HttpAsyncClient $htt $promise = new HttpRejectedPromise(new TransferException()); $httpAsyncClient->sendAsyncRequest($request)->willReturn($promise); - $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); + $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf(HttpRejectedPromise::class); $this->isDisabled()->shouldReturn(false); - $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); + $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf(HttpRejectedPromise::class); } public function it_increments_request_count(HttpAsyncClient $httpAsyncClient, RequestInterface $request, ResponseInterface $response) diff --git a/spec/HttpClientRouterSpec.php b/spec/HttpClientRouterSpec.php index e43acea..68446a4 100644 --- a/spec/HttpClientRouterSpec.php +++ b/spec/HttpClientRouterSpec.php @@ -9,22 +9,24 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\HttpClientRouter; +use Http\Client\Exception\RequestException; class HttpClientRouterSpec extends ObjectBehavior { public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\HttpClientRouter'); + $this->shouldHaveType(HttpClientRouter::class); } public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } public function it_is_an_async_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } public function it_send_request(RequestMatcher $matcher, HttpClient $client, RequestInterface $request, ResponseInterface $response) @@ -50,7 +52,7 @@ public function it_throw_exception_on_send_request(RequestMatcher $matcher, Http $this->addClient($client, $matcher); $matcher->matches($request)->willReturn(false); - $this->shouldThrow('Http\Client\Exception\RequestException')->duringSendRequest($request); + $this->shouldThrow(RequestException::class)->duringSendRequest($request); } public function it_throw_exception_on_send_async_request(RequestMatcher $matcher, HttpAsyncClient $client, RequestInterface $request) @@ -58,6 +60,6 @@ public function it_throw_exception_on_send_async_request(RequestMatcher $matcher $this->addClient($client, $matcher); $matcher->matches($request)->willReturn(false); - $this->shouldThrow('Http\Client\Exception\RequestException')->duringSendAsyncRequest($request); + $this->shouldThrow(RequestException::class)->duringSendAsyncRequest($request); } } diff --git a/spec/HttpMethodsClientSpec.php b/spec/HttpMethodsClientSpec.php index b68c8c4..74e0810 100644 --- a/spec/HttpMethodsClientSpec.php +++ b/spec/HttpMethodsClientSpec.php @@ -2,19 +2,20 @@ namespace spec\Http\Client\Common; -use Http\Client\HttpClient; +use GuzzleHttp\Psr7\Response; use Http\Client\Common\HttpMethodsClient; +use Http\Client\HttpClient; use Http\Message\MessageFactory; +use PhpSpec\ObjectBehavior; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use PhpSpec\ObjectBehavior; class HttpMethodsClientSpec extends ObjectBehavior { public function let(HttpClient $client, MessageFactory $messageFactory) { $this->beAnInstanceOf( - 'spec\Http\Client\Common\HttpMethodsClientStub', [ + HttpMethodsClientStub::class, [ $client, $messageFactory, ] @@ -25,56 +26,56 @@ public function it_sends_a_get_request() { $data = HttpMethodsClientStub::$requestData; - $this->get($data['uri'], $data['headers'])->shouldReturn(true); + $this->get($data['uri'], $data['headers'])->shouldReturnAnInstanceOf(ResponseInterface::class); } public function it_sends_a_head_request() { $data = HttpMethodsClientStub::$requestData; - $this->head($data['uri'], $data['headers'])->shouldReturn(true); + $this->head($data['uri'], $data['headers'])->shouldReturnAnInstanceOf(ResponseInterface::class); } public function it_sends_a_trace_request() { $data = HttpMethodsClientStub::$requestData; - $this->trace($data['uri'], $data['headers'])->shouldReturn(true); + $this->trace($data['uri'], $data['headers'])->shouldReturnAnInstanceOf(ResponseInterface::class); } public function it_sends_a_post_request() { $data = HttpMethodsClientStub::$requestData; - $this->post($data['uri'], $data['headers'], $data['body'])->shouldReturn(true); + $this->post($data['uri'], $data['headers'], $data['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); } public function it_sends_a_put_request() { $data = HttpMethodsClientStub::$requestData; - $this->put($data['uri'], $data['headers'], $data['body'])->shouldReturn(true); + $this->put($data['uri'], $data['headers'], $data['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); } public function it_sends_a_patch_request() { $data = HttpMethodsClientStub::$requestData; - $this->patch($data['uri'], $data['headers'], $data['body'])->shouldReturn(true); + $this->patch($data['uri'], $data['headers'], $data['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); } public function it_sends_a_delete_request() { $data = HttpMethodsClientStub::$requestData; - $this->delete($data['uri'], $data['headers'], $data['body'])->shouldReturn(true); + $this->delete($data['uri'], $data['headers'], $data['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); } public function it_sends_a_options_request() { $data = HttpMethodsClientStub::$requestData; - $this->options($data['uri'], $data['headers'], $data['body'])->shouldReturn(true); + $this->options($data['uri'], $data['headers'], $data['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); } public function it_sends_request_with_underlying_client(HttpClient $client, MessageFactory $messageFactory, RequestInterface $request, ResponseInterface $response) @@ -99,17 +100,37 @@ class HttpMethodsClientStub extends HttpMethodsClient /** * {@inheritdoc} */ - public function send($method, $uri, array $headers = [], $body = null) + public function send($method, $uri, array $headers = [], $body = null): ResponseInterface { - if (in_array($method, ['GET', 'HEAD', 'TRACE'])) { - return $uri === self::$requestData['uri'] && - $headers === self::$requestData['headers'] && - is_null($body); + if ($uri !== self::$requestData['uri']) { + throw new \InvalidArgumentException('Invalid URI: '.$uri); } - return in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']) && - $uri === self::$requestData['uri'] && - $headers === self::$requestData['headers'] && - $body === self::$requestData['body']; + if ($headers !== self::$requestData['headers']) { + throw new \InvalidArgumentException('Invalid headers: '.print_r($headers, true)); + } + + switch ($method) { + case 'GET': + case 'HEAD': + case 'TRACE': + if (null !== $body) { + throw new \InvalidArgumentException('Non-empty body'); + } + + return new Response(); + case 'POST': + case 'PUT': + case 'PATCH': + case 'DELETE': + case 'OPTIONS': + if ($body !== self::$requestData['body']) { + throw new \InvalidArgumentException('Invalid body: '.print_r($body, true)); + } + + return new Response(); + default: + throw new \InvalidArgumentException('Invalid method: '.$method); + } } } diff --git a/spec/Plugin/AddHostPluginSpec.php b/spec/Plugin/AddHostPluginSpec.php index a6630bd..2691f10 100644 --- a/spec/Plugin/AddHostPluginSpec.php +++ b/spec/Plugin/AddHostPluginSpec.php @@ -5,6 +5,8 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\Plugin\AddHostPlugin; +use Http\Client\Common\Plugin; class AddHostPluginSpec extends ObjectBehavior { @@ -17,14 +19,14 @@ public function it_is_initializable(UriInterface $uri) { $uri->getHost()->shouldBeCalled()->willReturn('example.com'); - $this->shouldHaveType('Http\Client\Common\Plugin\AddHostPlugin'); + $this->shouldHaveType(AddHostPlugin::class); } public function it_is_a_plugin(UriInterface $uri) { $uri->getHost()->shouldBeCalled()->willReturn('example.com'); - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_adds_domain( @@ -45,7 +47,7 @@ public function it_adds_domain( $uri->getHost()->shouldBeCalled()->willReturn(''); $this->beConstructedWith($host); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_replaces_domain( @@ -65,7 +67,7 @@ public function it_replaces_domain( $uri->withPort(8000)->shouldBeCalled()->willReturn($uri); $this->beConstructedWith($host, ['replace' => true]); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_nothing_when_domain_exists( @@ -77,6 +79,6 @@ public function it_does_nothing_when_domain_exists( $uri->getHost()->shouldBeCalled()->willReturn('default.com'); $this->beConstructedWith($host); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } } diff --git a/spec/Plugin/AddPathPluginSpec.php b/spec/Plugin/AddPathPluginSpec.php index c95301c..a76eb16 100644 --- a/spec/Plugin/AddPathPluginSpec.php +++ b/spec/Plugin/AddPathPluginSpec.php @@ -5,6 +5,8 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\Plugin\AddPathPlugin; +use Http\Client\Common\Plugin; class AddPathPluginSpec extends ObjectBehavior { @@ -17,14 +19,14 @@ public function it_is_initializable(UriInterface $uri) { $uri->getPath()->shouldBeCalled()->willReturn('/api'); - $this->shouldHaveType('Http\Client\Common\Plugin\AddPathPlugin'); + $this->shouldHaveType(AddPathPlugin::class); } public function it_is_a_plugin(UriInterface $uri) { $uri->getPath()->shouldBeCalled()->willReturn('/api'); - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_adds_path( @@ -41,7 +43,7 @@ public function it_adds_path( $uri->getPath()->shouldBeCalled()->willReturn('/users'); $this->beConstructedWith($host); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_throws_exception_on_trailing_slash(UriInterface $host) @@ -49,7 +51,7 @@ public function it_throws_exception_on_trailing_slash(UriInterface $host) $host->getPath()->shouldBeCalled()->willReturn('/api/'); $this->beConstructedWith($host); - $this->shouldThrow('\LogicException')->duringInstantiation(); + $this->shouldThrow(\LogicException::class)->duringInstantiation(); } public function it_throws_exception_on_empty_path(UriInterface $host) @@ -57,6 +59,6 @@ public function it_throws_exception_on_empty_path(UriInterface $host) $host->getPath()->shouldBeCalled()->willReturn(''); $this->beConstructedWith($host); - $this->shouldThrow('\LogicException')->duringInstantiation(); + $this->shouldThrow(\LogicException::class)->duringInstantiation(); } } diff --git a/spec/Plugin/AuthenticationPluginSpec.php b/spec/Plugin/AuthenticationPluginSpec.php index 549b507..c86cb3a 100644 --- a/spec/Plugin/AuthenticationPluginSpec.php +++ b/spec/Plugin/AuthenticationPluginSpec.php @@ -7,6 +7,8 @@ use Psr\Http\Message\RequestInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; +use Http\Client\Common\Plugin\AuthenticationPlugin; +use Http\Client\Common\Plugin; class AuthenticationPluginSpec extends ObjectBehavior { @@ -17,12 +19,12 @@ public function let(Authentication $authentication) public function it_is_initializable(Authentication $authentication) { - $this->shouldHaveType('Http\Client\Common\Plugin\AuthenticationPlugin'); + $this->shouldHaveType(AuthenticationPlugin::class); } public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_sends_an_authenticated_request(Authentication $authentication, RequestInterface $notAuthedRequest, RequestInterface $authedRequest, Promise $promise) diff --git a/spec/Plugin/BaseUriPluginSpec.php b/spec/Plugin/BaseUriPluginSpec.php index e2f8c46..92f5074 100644 --- a/spec/Plugin/BaseUriPluginSpec.php +++ b/spec/Plugin/BaseUriPluginSpec.php @@ -5,6 +5,8 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\Plugin\BaseUriPlugin; +use Http\Client\Common\Plugin; class BaseUriPluginSpec extends ObjectBehavior { @@ -18,7 +20,7 @@ public function it_is_initializable(UriInterface $uri) $uri->getHost()->shouldBeCalled()->willReturn('example.com'); $uri->getPath()->shouldBeCalled()->willReturn('/api'); - $this->shouldHaveType('Http\Client\Common\Plugin\BaseUriPlugin'); + $this->shouldHaveType(BaseUriPlugin::class); } public function it_is_a_plugin(UriInterface $uri) @@ -26,7 +28,7 @@ public function it_is_a_plugin(UriInterface $uri) $uri->getHost()->shouldBeCalled()->willReturn('example.com'); $uri->getPath()->shouldBeCalled()->willReturn('/api'); - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_adds_domain_and_path( @@ -50,7 +52,7 @@ public function it_adds_domain_and_path( $uri->getPath()->shouldBeCalled()->willReturn('/users'); $this->beConstructedWith($host); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_adds_domain( @@ -72,7 +74,7 @@ public function it_adds_domain( $uri->getHost()->shouldBeCalled()->willReturn(''); $this->beConstructedWith($host); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_replaces_domain_and_adds_path( @@ -95,6 +97,6 @@ public function it_replaces_domain_and_adds_path( $uri->getPath()->shouldBeCalled()->willReturn('/users'); $this->beConstructedWith($host, ['replace' => true]); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } } diff --git a/spec/Plugin/ContentLengthPluginSpec.php b/spec/Plugin/ContentLengthPluginSpec.php index 6624f6c..a945924 100644 --- a/spec/Plugin/ContentLengthPluginSpec.php +++ b/spec/Plugin/ContentLengthPluginSpec.php @@ -7,17 +7,19 @@ use Psr\Http\Message\StreamInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; +use Http\Client\Common\Plugin\ContentLengthPlugin; +use Http\Client\Common\Plugin; class ContentLengthPluginSpec extends ObjectBehavior { public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\ContentLengthPlugin'); + $this->shouldHaveType(ContentLengthPlugin::class); } public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_adds_content_length_header(RequestInterface $request, StreamInterface $stream) @@ -27,7 +29,7 @@ public function it_adds_content_length_header(RequestInterface $request, StreamI $stream->getSize()->shouldBeCalled()->willReturn(100); $request->withHeader('Content-Length', '100')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_streams_chunked_if_no_size(RequestInterface $request, StreamInterface $stream) @@ -43,6 +45,6 @@ public function it_streams_chunked_if_no_size(RequestInterface $request, StreamI $request->withBody(Argument::type('Http\Message\Encoding\ChunkStream'))->shouldBeCalled()->willReturn($request); $request->withAddedHeader('Transfer-Encoding', 'chunked')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } } diff --git a/spec/Plugin/ContentTypePluginSpec.php b/spec/Plugin/ContentTypePluginSpec.php index d2a6ffa..a27d32a 100644 --- a/spec/Plugin/ContentTypePluginSpec.php +++ b/spec/Plugin/ContentTypePluginSpec.php @@ -2,19 +2,21 @@ namespace spec\Http\Client\Common\Plugin; -use Psr\Http\Message\RequestInterface; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\ContentTypePlugin; use PhpSpec\ObjectBehavior; +use Psr\Http\Message\RequestInterface; class ContentTypePluginSpec extends ObjectBehavior { public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\ContentTypePlugin'); + $this->shouldHaveType(ContentTypePlugin::class); } public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_adds_json_content_type_header(RequestInterface $request) @@ -23,7 +25,7 @@ public function it_adds_json_content_type_header(RequestInterface $request) $request->getBody()->shouldBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for(json_encode(['foo' => 'bar']))); $request->withHeader('Content-Type', 'application/json')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_adds_xml_content_type_header(RequestInterface $request) @@ -32,7 +34,7 @@ public function it_adds_xml_content_type_header(RequestInterface $request) $request->getBody()->shouldBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for('bar')); $request->withHeader('Content-Type', 'application/xml')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_not_set_content_type_header(RequestInterface $request) @@ -41,7 +43,7 @@ public function it_does_not_set_content_type_header(RequestInterface $request) $request->getBody()->shouldBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for('foo')); $request->withHeader('Content-Type', null)->shouldNotBeCalled(); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_not_set_content_type_header_if_already_one(RequestInterface $request) @@ -50,7 +52,7 @@ public function it_does_not_set_content_type_header_if_already_one(RequestInterf $request->getBody()->shouldNotBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for('foo')); $request->withHeader('Content-Type', null)->shouldNotBeCalled(); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_not_set_content_type_header_if_size_0_or_unknown(RequestInterface $request) @@ -59,7 +61,7 @@ public function it_does_not_set_content_type_header_if_size_0_or_unknown(Request $request->getBody()->shouldBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for()); $request->withHeader('Content-Type', null)->shouldNotBeCalled(); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_adds_xml_content_type_header_if_size_limit_is_not_reached_using_default_value(RequestInterface $request) @@ -72,7 +74,7 @@ public function it_adds_xml_content_type_header_if_size_limit_is_not_reached_usi $request->getBody()->shouldBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for('bar')); $request->withHeader('Content-Type', 'application/xml')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_adds_xml_content_type_header_if_size_limit_is_not_reached(RequestInterface $request) @@ -86,7 +88,7 @@ public function it_adds_xml_content_type_header_if_size_limit_is_not_reached(Req $request->getBody()->shouldBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for('bar')); $request->withHeader('Content-Type', 'application/xml')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_not_set_content_type_header_if_size_limit_is_reached(RequestInterface $request) @@ -100,6 +102,6 @@ public function it_does_not_set_content_type_header_if_size_limit_is_reached(Req $request->getBody()->shouldBeCalled()->willReturn(\GuzzleHttp\Psr7\stream_for('bar')); $request->withHeader('Content-Type', null)->shouldNotBeCalled(); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } } diff --git a/spec/Plugin/CookiePluginSpec.php b/spec/Plugin/CookiePluginSpec.php index 1f021b1..7bb188c 100644 --- a/spec/Plugin/CookiePluginSpec.php +++ b/spec/Plugin/CookiePluginSpec.php @@ -10,7 +10,10 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; +use Http\Client\Common\Plugin\CookiePlugin; +use Http\Client\Common\Plugin; +use Http\Client\Promise\HttpRejectedPromise; +use Http\Client\Exception\TransferException; class CookiePluginSpec extends ObjectBehavior { @@ -25,12 +28,12 @@ public function let() public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\CookiePlugin'); + $this->shouldHaveType(CookiePlugin::class); } public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_loads_cookie(RequestInterface $request, UriInterface $uri, Promise $promise) @@ -44,11 +47,7 @@ public function it_loads_cookie(RequestInterface $request, UriInterface $uri, Pr $request->withAddedHeader('Cookie', 'name=value')->willReturn($request); - $this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) { - if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) { - return $promise->getWrappedObject(); - } - }, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_combines_multiple_cookies_into_one_header(RequestInterface $request, UriInterface $uri, Promise $promise) @@ -65,11 +64,7 @@ public function it_combines_multiple_cookies_into_one_header(RequestInterface $r $request->withAddedHeader('Cookie', 'name=value; name2=value2')->willReturn($request); - $this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) { - if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) { - return $promise->getWrappedObject(); - } - }, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_not_load_cookie_if_expired(RequestInterface $request, UriInterface $uri, Promise $promise) @@ -79,11 +74,7 @@ public function it_does_not_load_cookie_if_expired(RequestInterface $request, Ur $request->withAddedHeader('Cookie', 'name=value')->shouldNotBeCalled(); - $this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) { - if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) { - return $promise->getWrappedObject(); - } - }, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_not_load_cookie_if_domain_does_not_match(RequestInterface $request, UriInterface $uri, Promise $promise) @@ -96,11 +87,7 @@ public function it_does_not_load_cookie_if_domain_does_not_match(RequestInterfac $request->withAddedHeader('Cookie', 'name=value')->shouldNotBeCalled(); - $this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) { - if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) { - return $promise->getWrappedObject(); - } - }, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_not_load_cookie_on_hackish_domains(RequestInterface $request, UriInterface $uri, Promise $promise) @@ -118,11 +105,7 @@ public function it_does_not_load_cookie_on_hackish_domains(RequestInterface $req $request->withAddedHeader('Cookie', 'name=value')->shouldNotBeCalled(); - $this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) { - if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) { - return $promise->getWrappedObject(); - } - }, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } } @@ -137,11 +120,7 @@ public function it_loads_cookie_on_subdomains(RequestInterface $request, UriInte $request->withAddedHeader('Cookie', 'name=value')->willReturn($request); - $this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) { - if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) { - return $promise->getWrappedObject(); - } - }, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_not_load_cookie_if_path_does_not_match(RequestInterface $request, UriInterface $uri, Promise $promise) @@ -155,11 +134,7 @@ public function it_does_not_load_cookie_if_path_does_not_match(RequestInterface $request->withAddedHeader('Cookie', 'name=value')->shouldNotBeCalled(); - $this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) { - if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) { - return $promise->getWrappedObject(); - } - }, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_not_load_cookie_when_cookie_is_secure(RequestInterface $request, UriInterface $uri, Promise $promise) @@ -174,11 +149,7 @@ public function it_does_not_load_cookie_when_cookie_is_secure(RequestInterface $ $request->withAddedHeader('Cookie', 'name=value')->shouldNotBeCalled(); - $this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) { - if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) { - return $promise->getWrappedObject(); - } - }, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_loads_cookie_when_cookie_is_secure(RequestInterface $request, UriInterface $uri, Promise $promise) @@ -193,11 +164,7 @@ public function it_loads_cookie_when_cookie_is_secure(RequestInterface $request, $request->withAddedHeader('Cookie', 'name=value')->willReturn($request); - $this->handleRequest($request, function (RequestInterface $requestReceived) use ($request, $promise) { - if (Argument::is($requestReceived)->scoreArgument($request->getWrappedObject())) { - return $promise->getWrappedObject(); - } - }, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_saves_cookie(RequestInterface $request, ResponseInterface $response, UriInterface $uri) @@ -216,8 +183,8 @@ public function it_saves_cookie(RequestInterface $request, ResponseInterface $re $uri->getPath()->willReturn('/'); $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldHaveType('Http\Promise\Promise'); - $promise->wait()->shouldReturnAnInstanceOf('Psr\Http\Message\ResponseInterface'); + $promise->shouldHaveType(Promise::class); + $promise->wait()->shouldReturnAnInstanceOf(ResponseInterface::class); } public function it_throws_exception_on_invalid_expires_date( @@ -239,7 +206,7 @@ public function it_throws_exception_on_invalid_expires_date( $uri->getPath()->willReturn('/'); $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); - $promise->shouldThrow('Http\Client\Exception\TransferException')->duringWait(); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); + $promise->shouldThrow(TransferException::class)->duringWait(); } } diff --git a/spec/Plugin/DecoderPluginSpec.php b/spec/Plugin/DecoderPluginSpec.php index 4de938c..1316a90 100644 --- a/spec/Plugin/DecoderPluginSpec.php +++ b/spec/Plugin/DecoderPluginSpec.php @@ -9,17 +9,21 @@ use PhpSpec\Exception\Example\SkippingException; use PhpSpec\ObjectBehavior; use Prophecy\Argument; +use Http\Client\Common\Plugin\DecoderPlugin; +use Http\Client\Common\Plugin; +use Http\Message\Encoding\GzipDecodeStream; +use Http\Message\Encoding\DecompressStream; class DecoderPluginSpec extends ObjectBehavior { public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\DecoderPlugin'); + $this->shouldHaveType(DecoderPlugin::class); } public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_decodes(RequestInterface $request, ResponseInterface $response, StreamInterface $stream) @@ -60,7 +64,7 @@ public function it_decodes_gzip(RequestInterface $request, ResponseInterface $re $response->hasHeader('Content-Encoding')->willReturn(true); $response->getHeader('Content-Encoding')->willReturn(['gzip']); $response->getBody()->willReturn($stream); - $response->withBody(Argument::type('Http\Message\Encoding\GzipDecodeStream'))->willReturn($response); + $response->withBody(Argument::type(GzipDecodeStream::class))->willReturn($response); $response->withoutHeader('Content-Encoding')->willReturn($response); $stream->isReadable()->willReturn(true); @@ -82,7 +86,7 @@ public function it_decodes_deflate(RequestInterface $request, ResponseInterface $response->hasHeader('Content-Encoding')->willReturn(true); $response->getHeader('Content-Encoding')->willReturn(['deflate']); $response->getBody()->willReturn($stream); - $response->withBody(Argument::type('Http\Message\Encoding\DecompressStream'))->willReturn($response); + $response->withBody(Argument::type(DecompressStream::class))->willReturn($response); $response->withoutHeader('Content-Encoding')->willReturn($response); $stream->isReadable()->willReturn(true); diff --git a/spec/Plugin/ErrorPluginSpec.php b/spec/Plugin/ErrorPluginSpec.php index 5a9e90a..67e5c7e 100644 --- a/spec/Plugin/ErrorPluginSpec.php +++ b/spec/Plugin/ErrorPluginSpec.php @@ -7,17 +7,22 @@ use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; +use Http\Client\Common\Plugin\ErrorPlugin; +use Http\Client\Common\Plugin; +use Http\Client\Promise\HttpRejectedPromise; +use Http\Client\Common\Exception\ClientErrorException; +use Http\Client\Common\Exception\ServerErrorException; class ErrorPluginSpec extends ObjectBehavior { public function it_is_initializable() { - $this->beAnInstanceOf('Http\Client\Common\Plugin\ErrorPlugin'); + $this->beAnInstanceOf(ErrorPlugin::class); } public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_throw_client_error_exception_on_4xx_error(RequestInterface $request, ResponseInterface $response) @@ -32,8 +37,8 @@ public function it_throw_client_error_exception_on_4xx_error(RequestInterface $r }; $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); - $promise->shouldThrow('Http\Client\Common\Exception\ClientErrorException')->duringWait(); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); + $promise->shouldThrow(ClientErrorException::class)->duringWait(); } public function it_does_not_throw_client_error_exception_on_4xx_error_if_only_server_exception(RequestInterface $request, ResponseInterface $response) @@ -49,7 +54,7 @@ public function it_does_not_throw_client_error_exception_on_4xx_error_if_only_se } }; - $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); } public function it_throw_server_error_exception_on_5xx_error(RequestInterface $request, ResponseInterface $response) @@ -64,8 +69,8 @@ public function it_throw_server_error_exception_on_5xx_error(RequestInterface $r }; $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); - $promise->shouldThrow('Http\Client\Common\Exception\ServerErrorException')->duringWait(); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); + $promise->shouldThrow(ServerErrorException::class)->duringWait(); } public function it_returns_response(RequestInterface $request, ResponseInterface $response) @@ -78,6 +83,6 @@ public function it_returns_response(RequestInterface $request, ResponseInterface } }; - $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); } } diff --git a/spec/Plugin/HeaderAppendPluginSpec.php b/spec/Plugin/HeaderAppendPluginSpec.php index bb10de1..9325069 100644 --- a/spec/Plugin/HeaderAppendPluginSpec.php +++ b/spec/Plugin/HeaderAppendPluginSpec.php @@ -2,21 +2,23 @@ namespace spec\Http\Client\Common\Plugin; -use Psr\Http\Message\RequestInterface; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\HeaderAppendPlugin; use PhpSpec\ObjectBehavior; +use Psr\Http\Message\RequestInterface; class HeaderAppendPluginSpec extends ObjectBehavior { public function it_is_initializable() { $this->beConstructedWith([]); - $this->shouldHaveType('Http\Client\Common\Plugin\HeaderAppendPlugin'); + $this->shouldHaveType(HeaderAppendPlugin::class); } public function it_is_a_plugin() { $this->beConstructedWith([]); - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_appends_the_header(RequestInterface $request) @@ -29,6 +31,6 @@ public function it_appends_the_header(RequestInterface $request) $request->withAddedHeader('foo', 'bar')->shouldBeCalled()->willReturn($request); $request->withAddedHeader('baz', 'qux')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } } diff --git a/spec/Plugin/HeaderDefaultsPluginSpec.php b/spec/Plugin/HeaderDefaultsPluginSpec.php index cd407e3..5a50a9c 100644 --- a/spec/Plugin/HeaderDefaultsPluginSpec.php +++ b/spec/Plugin/HeaderDefaultsPluginSpec.php @@ -2,21 +2,23 @@ namespace spec\Http\Client\Common\Plugin; -use Psr\Http\Message\RequestInterface; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\HeaderDefaultsPlugin; use PhpSpec\ObjectBehavior; +use Psr\Http\Message\RequestInterface; class HeaderDefaultsPluginSpec extends ObjectBehavior { public function it_is_initializable() { $this->beConstructedWith([]); - $this->shouldHaveType('Http\Client\Common\Plugin\HeaderDefaultsPlugin'); + $this->shouldHaveType(HeaderDefaultsPlugin::class); } public function it_is_a_plugin() { $this->beConstructedWith([]); - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_sets_the_default_header(RequestInterface $request) @@ -30,6 +32,6 @@ public function it_sets_the_default_header(RequestInterface $request) $request->withHeader('foo', 'bar')->shouldBeCalled()->willReturn($request); $request->hasHeader('baz')->shouldBeCalled()->willReturn(true); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } } diff --git a/spec/Plugin/HeaderRemovePluginSpec.php b/spec/Plugin/HeaderRemovePluginSpec.php index ec6b069..3f60359 100644 --- a/spec/Plugin/HeaderRemovePluginSpec.php +++ b/spec/Plugin/HeaderRemovePluginSpec.php @@ -2,21 +2,23 @@ namespace spec\Http\Client\Common\Plugin; -use Psr\Http\Message\RequestInterface; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\HeaderRemovePlugin; use PhpSpec\ObjectBehavior; +use Psr\Http\Message\RequestInterface; class HeaderRemovePluginSpec extends ObjectBehavior { public function it_is_initializable() { $this->beConstructedWith([]); - $this->shouldHaveType('Http\Client\Common\Plugin\HeaderRemovePlugin'); + $this->shouldHaveType(HeaderRemovePlugin::class); } public function it_is_a_plugin() { $this->beConstructedWith([]); - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_removes_the_header(RequestInterface $request) @@ -31,6 +33,6 @@ public function it_removes_the_header(RequestInterface $request) $request->hasHeader('baz')->shouldBeCalled()->willReturn(true); $request->withoutHeader('baz')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } } diff --git a/spec/Plugin/HeaderSetPluginSpec.php b/spec/Plugin/HeaderSetPluginSpec.php index 4e4efd0..b152567 100644 --- a/spec/Plugin/HeaderSetPluginSpec.php +++ b/spec/Plugin/HeaderSetPluginSpec.php @@ -2,21 +2,23 @@ namespace spec\Http\Client\Common\Plugin; -use Psr\Http\Message\RequestInterface; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\HeaderSetPlugin; use PhpSpec\ObjectBehavior; +use Psr\Http\Message\RequestInterface; class HeaderSetPluginSpec extends ObjectBehavior { public function it_is_initializable() { $this->beConstructedWith([]); - $this->shouldHaveType('Http\Client\Common\Plugin\HeaderSetPlugin'); + $this->shouldHaveType(HeaderSetPlugin::class); } public function it_is_a_plugin() { $this->beConstructedWith([]); - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_set_the_header(RequestInterface $request) @@ -29,6 +31,6 @@ public function it_set_the_header(RequestInterface $request) $request->withHeader('foo', 'bar')->shouldBeCalled()->willReturn($request); $request->withHeader('baz', 'qux')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } } diff --git a/spec/Plugin/HistoryPluginSpec.php b/spec/Plugin/HistoryPluginSpec.php index 77682a7..495e5d5 100644 --- a/spec/Plugin/HistoryPluginSpec.php +++ b/spec/Plugin/HistoryPluginSpec.php @@ -10,6 +10,7 @@ use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; +use Http\Client\Common\Plugin; class HistoryPluginSpec extends ObjectBehavior { @@ -25,7 +26,7 @@ public function it_is_initializable() public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_records_success(Journal $journal, RequestInterface $request, ResponseInterface $response) diff --git a/spec/Plugin/PluginStub.php b/spec/Plugin/PluginStub.php new file mode 100644 index 0000000..ead2a57 --- /dev/null +++ b/spec/Plugin/PluginStub.php @@ -0,0 +1,25 @@ +shouldHaveType('Http\Client\Common\Plugin\QueryDefaultsPlugin'); + $this->shouldHaveType(QueryDefaultsPlugin::class); } public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_sets_the_default_header(RequestInterface $request, UriInterface $uri) @@ -34,9 +36,7 @@ public function it_sets_the_default_header(RequestInterface $request, UriInterfa $uri->withQuery('test=true&foo=bar')->shouldBeCalled()->willReturn($uri); $request->withUri($uri)->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () { - }, function () { - }); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_not_replace_existing_request_value(RequestInterface $request, UriInterface $uri) @@ -51,8 +51,6 @@ public function it_does_not_replace_existing_request_value(RequestInterface $req $uri->withQuery('foo=new&bar=barDefault')->shouldBeCalled()->willReturn($uri); $request->withUri($uri)->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () { - }, function () { - }); + $this->handleRequest($request, PluginStub::next(), function () {}); } } diff --git a/spec/Plugin/RedirectPluginSpec.php b/spec/Plugin/RedirectPluginSpec.php index 283bfb5..e24b6b1 100644 --- a/spec/Plugin/RedirectPluginSpec.php +++ b/spec/Plugin/RedirectPluginSpec.php @@ -2,25 +2,30 @@ namespace spec\Http\Client\Common\Plugin; +use Http\Client\Common\Exception\CircularRedirectionException; +use Http\Client\Common\Exception\MultipleRedirectionException; +use Http\Client\Common\Plugin; use Http\Client\Common\Plugin\RedirectPlugin; +use Http\Client\Exception\HttpException; use Http\Client\Promise\HttpFulfilledPromise; +use Http\Client\Promise\HttpRejectedPromise; use Http\Promise\Promise; +use PhpSpec\ObjectBehavior; +use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; -use PhpSpec\ObjectBehavior; -use Prophecy\Argument; class RedirectPluginSpec extends ObjectBehavior { public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\RedirectPlugin'); + $this->shouldHaveType(RedirectPlugin::class); } public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_redirects_on_302( @@ -64,13 +69,13 @@ public function it_redirects_on_302( $promise->wait()->shouldBeCalled()->willReturn($finalResponse); $finalPromise = $this->handleRequest($request, $next, $first); - $finalPromise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $finalPromise->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); $finalPromise->wait()->shouldReturn($finalResponse); } public function it_use_storage_on_301(UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, RequestInterface $modifiedRequest) { - $this->beAnInstanceOf('spec\Http\Client\Common\Plugin\RedirectPluginStub'); + $this->beAnInstanceOf(RedirectPluginStub::class); $this->beConstructedWith($uriRedirect, '/original', '301'); $next = function () { @@ -86,7 +91,7 @@ public function it_use_storage_on_301(UriInterface $uri, UriInterface $uriRedire $uriRedirect->__toString()->willReturn('/redirect'); - $this->handleRequest($request, $next, function () {}); + $this->handleRequest($request, $next, PluginStub::first()); } public function it_stores_a_301( @@ -98,7 +103,7 @@ public function it_stores_a_301( ResponseInterface $finalResponse, Promise $promise ) { - $this->beAnInstanceOf('spec\Http\Client\Common\Plugin\RedirectPluginStub'); + $this->beAnInstanceOf(RedirectPluginStub::class); $this->beConstructedWith($uriRedirect, '', '301'); $request->getUri()->willReturn($uri); @@ -201,8 +206,8 @@ public function it_throws_http_exception_on_no_location(RequestInterface $reques $responseRedirect->hasHeader('Location')->willReturn(false); $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); - $promise->shouldThrow('Http\Client\Exception\HttpException')->duringWait(); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); + $promise->shouldThrow(HttpException::class)->duringWait(); } public function it_throws_http_exception_on_invalid_location(RequestInterface $request, UriInterface $uri, ResponseInterface $responseRedirect) @@ -221,8 +226,8 @@ public function it_throws_http_exception_on_invalid_location(RequestInterface $r $responseRedirect->hasHeader('Location')->willReturn(true); $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); - $promise->shouldThrow('Http\Client\Exception\HttpException')->duringWait(); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); + $promise->shouldThrow(HttpException::class)->duringWait(); } public function it_throw_multi_redirect_exception_on_300(RequestInterface $request, ResponseInterface $responseRedirect) @@ -237,8 +242,8 @@ public function it_throw_multi_redirect_exception_on_300(RequestInterface $reque $responseRedirect->getStatusCode()->willReturn('300'); $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); - $promise->shouldThrow('Http\Client\Common\Exception\MultipleRedirectionException')->duringWait(); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); + $promise->shouldThrow(MultipleRedirectionException::class)->duringWait(); } public function it_throw_multi_redirect_exception_on_300_if_no_location(RequestInterface $request, ResponseInterface $responseRedirect) @@ -253,8 +258,8 @@ public function it_throw_multi_redirect_exception_on_300_if_no_location(RequestI $responseRedirect->hasHeader('Location')->willReturn(false); $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); - $promise->shouldThrow('Http\Client\Common\Exception\MultipleRedirectionException')->duringWait(); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); + $promise->shouldThrow(MultipleRedirectionException::class)->duringWait(); } public function it_switch_method_for_302( @@ -358,7 +363,7 @@ public function it_throws_circular_redirection_exception(UriInterface $uri, UriI { $first = function () {}; - $this->beAnInstanceOf('spec\Http\Client\Common\Plugin\RedirectPluginStubCircular'); + $this->beAnInstanceOf(RedirectPluginStubCircular::class); $this->beConstructedWith(spl_object_hash((object) $first)); $request->getUri()->willReturn($uri); @@ -384,8 +389,8 @@ public function it_throws_circular_redirection_exception(UriInterface $uri, UriI }; $promise = $this->handleRequest($request, $next, $first); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); - $promise->shouldThrow('Http\Client\Common\Exception\CircularRedirectionException')->duringWait(); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); + $promise->shouldThrow(CircularRedirectionException::class)->duringWait(); } public function it_redirects_http_to_https( @@ -431,7 +436,7 @@ public function it_redirects_http_to_https( $promise->wait()->shouldBeCalled()->willReturn($finalResponse); $finalPromise = $this->handleRequest($request, $next, $first); - $finalPromise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $finalPromise->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); $finalPromise->wait()->shouldReturn($finalResponse); } } diff --git a/spec/Plugin/RequestMatcherPluginSpec.php b/spec/Plugin/RequestMatcherPluginSpec.php index bd4e7e4..de7cae6 100644 --- a/spec/Plugin/RequestMatcherPluginSpec.php +++ b/spec/Plugin/RequestMatcherPluginSpec.php @@ -8,6 +8,7 @@ use Psr\Http\Message\RequestInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; +use Http\Client\Common\Plugin\RequestMatcherPlugin; class RequestMatcherPluginSpec extends ObjectBehavior { @@ -18,12 +19,12 @@ public function let(RequestMatcher $requestMatcher, Plugin $plugin) public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\RequestMatcherPlugin'); + $this->shouldHaveType(RequestMatcherPlugin::class); } public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_matches_a_request_and_delegates_to_plugin( @@ -34,7 +35,7 @@ public function it_matches_a_request_and_delegates_to_plugin( $requestMatcher->matches($request)->willReturn(true); $plugin->handleRequest($request, Argument::type('callable'), Argument::type('callable'))->shouldBeCalled(); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_does_not_match_a_request( diff --git a/spec/Plugin/RetryPluginSpec.php b/spec/Plugin/RetryPluginSpec.php index c439cad..682b892 100644 --- a/spec/Plugin/RetryPluginSpec.php +++ b/spec/Plugin/RetryPluginSpec.php @@ -9,17 +9,19 @@ use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; +use Http\Client\Common\Plugin\RetryPlugin; +use Http\Client\Common\Plugin; class RetryPluginSpec extends ObjectBehavior { public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\RetryPlugin'); + $this->shouldHaveType(RetryPlugin::class); } public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } public function it_returns_response(RequestInterface $request, ResponseInterface $response) @@ -30,7 +32,7 @@ public function it_returns_response(RequestInterface $request, ResponseInterface } }; - $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); } public function it_throws_exception_on_multiple_exceptions(RequestInterface $request) @@ -53,7 +55,7 @@ public function it_throws_exception_on_multiple_exceptions(RequestInterface $req }; $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); $promise->shouldThrow($exception2)->duringWait(); } @@ -76,7 +78,7 @@ public function it_returns_response_on_second_try(RequestInterface $request, Res }; $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $promise->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); $promise->wait()->shouldReturn($response); } @@ -98,8 +100,8 @@ public function it_does_not_keep_history_of_old_failure(RequestInterface $reques } }; - $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); - $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); + $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); } public function it_has_an_exponential_default_delay(RequestInterface $request, Exception\HttpException $exception) diff --git a/spec/PluginClientFactorySpec.php b/spec/PluginClientFactorySpec.php index fe7e076..d7a3acb 100644 --- a/spec/PluginClientFactorySpec.php +++ b/spec/PluginClientFactorySpec.php @@ -4,19 +4,21 @@ use Http\Client\HttpClient; use PhpSpec\ObjectBehavior; +use Http\Client\Common\PluginClientFactory; +use Http\Client\Common\PluginClient; class PluginClientFactorySpec extends ObjectBehavior { public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\PluginClientFactory'); + $this->shouldHaveType(PluginClientFactory::class); } public function it_returns_a_plugin_client(HttpClient $httpClient) { $client = $this->createClient($httpClient); - $client->shouldHaveType('Http\Client\Common\PluginClient'); + $client->shouldHaveType(PluginClient::class); } public function it_does_not_construct_plugin_client_with_client_name_option(HttpClient $httpClient) diff --git a/spec/PluginClientSpec.php b/spec/PluginClientSpec.php index 50df636..c5c2932 100644 --- a/spec/PluginClientSpec.php +++ b/spec/PluginClientSpec.php @@ -10,6 +10,8 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\Exception\LoopException; +use Http\Client\Common\PluginClient; class PluginClientSpec extends ObjectBehavior { @@ -20,17 +22,17 @@ public function let(HttpClient $httpClient) public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\PluginClient'); + $this->shouldHaveType(PluginClient::class); } public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } public function it_is_an_http_async_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } public function it_sends_request_with_underlying_client(HttpClient $httpClient, RequestInterface $request, ResponseInterface $response) @@ -59,8 +61,8 @@ public function it_sends_async_request_if_no_send_request(HttpAsyncClient $httpA public function it_prefers_send_request($client, RequestInterface $request, ResponseInterface $response) { - $client->implement('Http\Client\HttpClient'); - $client->implement('Http\Client\HttpAsyncClient'); + $client->implement(HttpClient::class); + $client->implement(HttpAsyncClient::class); $client->sendRequest($request)->willReturn($response); @@ -84,6 +86,6 @@ public function it_throws_loop_exception(HttpClient $httpClient, RequestInterfac $this->beConstructedWith($httpClient, [$plugin]); - $this->shouldThrow('Http\Client\Common\Exception\LoopException')->duringSendRequest($request); + $this->shouldThrow(LoopException::class)->duringSendRequest($request); } } diff --git a/src/BatchClient.php b/src/BatchClient.php index 7ddba4e..8dfeb81 100644 --- a/src/BatchClient.php +++ b/src/BatchClient.php @@ -49,7 +49,7 @@ public function sendRequest(RequestInterface $request): ResponseInterface * BatchResult with a map of request to result for success, request to * exception for failures */ - public function sendRequests(array $requests) + public function sendRequests(array $requests): BatchResult { $batchResult = new BatchResult(); diff --git a/src/BatchResult.php b/src/BatchResult.php index 3f8a2f0..94ff51a 100644 --- a/src/BatchResult.php +++ b/src/BatchResult.php @@ -31,10 +31,8 @@ public function __construct() /** * Checks if there are any successful responses at all. - * - * @return bool */ - public function hasResponses() + public function hasResponses(): bool { return $this->responses->count() > 0; } @@ -44,7 +42,7 @@ public function hasResponses() * * @return ResponseInterface[] */ - public function getResponses() + public function getResponses(): array { $responses = []; @@ -57,10 +55,8 @@ public function getResponses() /** * Checks if there is a successful response for a request. - * - * @return bool */ - public function isSuccessful(RequestInterface $request) + public function isSuccessful(RequestInterface $request): bool { return $this->responses->contains($request); } @@ -68,11 +64,10 @@ public function isSuccessful(RequestInterface $request) /** * Returns the response for a successful request. * - * @return ResponseInterface * * @throws \UnexpectedValueException If request was not part of the batch or failed */ - public function getResponseFor(RequestInterface $request) + public function getResponseFor(RequestInterface $request): ResponseInterface { try { return $this->responses[$request]; @@ -86,7 +81,7 @@ public function getResponseFor(RequestInterface $request) * * @return BatchResult the new BatchResult with this request-response pair added to it */ - public function addResponse(RequestInterface $request, ResponseInterface $response) + public function addResponse(RequestInterface $request, ResponseInterface $response): self { $new = clone $this; $new->responses->attach($request, $response); @@ -96,10 +91,8 @@ public function addResponse(RequestInterface $request, ResponseInterface $respon /** * Checks if there are any unsuccessful requests at all. - * - * @return bool */ - public function hasExceptions() + public function hasExceptions(): bool { return $this->exceptions->count() > 0; } @@ -109,7 +102,7 @@ public function hasExceptions() * * @return Exception[] */ - public function getExceptions() + public function getExceptions(): array { $exceptions = []; @@ -122,10 +115,8 @@ public function getExceptions() /** * Checks if there is an exception for a request, meaning the request failed. - * - * @return bool */ - public function isFailed(RequestInterface $request) + public function isFailed(RequestInterface $request): bool { return $this->exceptions->contains($request); } @@ -133,11 +124,10 @@ public function isFailed(RequestInterface $request) /** * Returns the exception for a failed request. * - * @return Exception * * @throws \UnexpectedValueException If request was not part of the batch or was successful */ - public function getExceptionFor(RequestInterface $request) + public function getExceptionFor(RequestInterface $request): Exception { try { return $this->exceptions[$request]; @@ -151,7 +141,7 @@ public function getExceptionFor(RequestInterface $request) * * @return BatchResult the new BatchResult with this request-exception pair added to it */ - public function addException(RequestInterface $request, Exception $exception) + public function addException(RequestInterface $request, Exception $exception): self { $new = clone $this; $new->exceptions->attach($request, $exception); diff --git a/src/Deferred.php b/src/Deferred.php index 075a30e..7413451 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -34,7 +34,7 @@ public function __construct(callable $waitCallback) /** * {@inheritdoc} */ - public function then(callable $onFulfilled = null, callable $onRejected = null) + public function then(callable $onFulfilled = null, callable $onRejected = null): Promise { $deferred = new self($this->waitCallback); @@ -69,7 +69,7 @@ public function then(callable $onFulfilled = null, callable $onRejected = null) /** * {@inheritdoc} */ - public function getState() + public function getState(): string { return $this->state; } diff --git a/src/HttpClientPool.php b/src/HttpClientPool.php index 6f4597f..90a8464 100644 --- a/src/HttpClientPool.php +++ b/src/HttpClientPool.php @@ -40,7 +40,7 @@ public function addHttpClient($client) * * @return HttpClientPoolItem Return a http client that can do both sync or async */ - abstract protected function chooseHttpClient(); + abstract protected function chooseHttpClient(): HttpClientPoolItem; /** * {@inheritdoc} diff --git a/src/HttpClientPool/LeastUsedClientPool.php b/src/HttpClientPool/LeastUsedClientPool.php index 6299cce..61eb670 100644 --- a/src/HttpClientPool/LeastUsedClientPool.php +++ b/src/HttpClientPool/LeastUsedClientPool.php @@ -18,7 +18,7 @@ final class LeastUsedClientPool extends HttpClientPool /** * {@inheritdoc} */ - protected function chooseHttpClient() + protected function chooseHttpClient(): HttpClientPoolItem { $clientPool = array_filter($this->clientPool, function (HttpClientPoolItem $clientPoolItem) { return !$clientPoolItem->isDisabled(); diff --git a/src/HttpClientPool/RandomClientPool.php b/src/HttpClientPool/RandomClientPool.php index 3255f86..2889ed7 100644 --- a/src/HttpClientPool/RandomClientPool.php +++ b/src/HttpClientPool/RandomClientPool.php @@ -16,7 +16,7 @@ final class RandomClientPool extends HttpClientPool /** * {@inheritdoc} */ - protected function chooseHttpClient() + protected function chooseHttpClient(): HttpClientPoolItem { $clientPool = array_filter($this->clientPool, function (HttpClientPoolItem $clientPoolItem) { return !$clientPoolItem->isDisabled(); diff --git a/src/HttpClientPool/RoundRobinClientPool.php b/src/HttpClientPool/RoundRobinClientPool.php index 8d8e40a..3315b51 100644 --- a/src/HttpClientPool/RoundRobinClientPool.php +++ b/src/HttpClientPool/RoundRobinClientPool.php @@ -4,6 +4,7 @@ use Http\Client\Common\Exception\HttpClientNotFoundException; use Http\Client\Common\HttpClientPool; +use Http\Client\Common\HttpClientPoolItem; /** * RoundRobinClientPool will choose the next client in the pool. @@ -15,7 +16,7 @@ final class RoundRobinClientPool extends HttpClientPool /** * {@inheritdoc} */ - protected function chooseHttpClient() + protected function chooseHttpClient(): HttpClientPoolItem { $last = current($this->clientPool); diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index a1ef2c8..bc02ef8 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -52,10 +52,8 @@ public function __construct(HttpClient $httpClient, RequestFactory $requestFacto * @param string|UriInterface $uri * * @throws Exception - * - * @return ResponseInterface */ - public function get($uri, array $headers = []) + public function get($uri, array $headers = []): ResponseInterface { return $this->send('GET', $uri, $headers, null); } @@ -66,10 +64,8 @@ public function get($uri, array $headers = []) * @param string|UriInterface $uri * * @throws Exception - * - * @return ResponseInterface */ - public function head($uri, array $headers = []) + public function head($uri, array $headers = []): ResponseInterface { return $this->send('HEAD', $uri, $headers, null); } @@ -80,10 +76,8 @@ public function head($uri, array $headers = []) * @param string|UriInterface $uri * * @throws Exception - * - * @return ResponseInterface */ - public function trace($uri, array $headers = []) + public function trace($uri, array $headers = []): ResponseInterface { return $this->send('TRACE', $uri, $headers, null); } @@ -95,10 +89,8 @@ public function trace($uri, array $headers = []) * @param string|StreamInterface|null $body * * @throws Exception - * - * @return ResponseInterface */ - public function post($uri, array $headers = [], $body = null) + public function post($uri, array $headers = [], $body = null): ResponseInterface { return $this->send('POST', $uri, $headers, $body); } @@ -110,10 +102,8 @@ public function post($uri, array $headers = [], $body = null) * @param string|StreamInterface|null $body * * @throws Exception - * - * @return ResponseInterface */ - public function put($uri, array $headers = [], $body = null) + public function put($uri, array $headers = [], $body = null): ResponseInterface { return $this->send('PUT', $uri, $headers, $body); } @@ -125,10 +115,8 @@ public function put($uri, array $headers = [], $body = null) * @param string|StreamInterface|null $body * * @throws Exception - * - * @return ResponseInterface */ - public function patch($uri, array $headers = [], $body = null) + public function patch($uri, array $headers = [], $body = null): ResponseInterface { return $this->send('PATCH', $uri, $headers, $body); } @@ -140,10 +128,8 @@ public function patch($uri, array $headers = [], $body = null) * @param string|StreamInterface|null $body * * @throws Exception - * - * @return ResponseInterface */ - public function delete($uri, array $headers = [], $body = null) + public function delete($uri, array $headers = [], $body = null): ResponseInterface { return $this->send('DELETE', $uri, $headers, $body); } @@ -155,10 +141,8 @@ public function delete($uri, array $headers = [], $body = null) * @param string|StreamInterface|null $body * * @throws Exception - * - * @return ResponseInterface */ - public function options($uri, array $headers = [], $body = null) + public function options($uri, array $headers = [], $body = null): ResponseInterface { return $this->send('OPTIONS', $uri, $headers, $body); } @@ -171,10 +155,8 @@ public function options($uri, array $headers = [], $body = null) * @param string|StreamInterface|null $body * * @throws Exception - * - * @return ResponseInterface */ - public function send($method, $uri, array $headers = [], $body = null) + public function send($method, $uri, array $headers = [], $body = null): ResponseInterface { return $this->sendRequest($this->requestFactory->createRequest( $method, diff --git a/src/Plugin.php b/src/Plugin.php index bfb2668..ab7e4a1 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -27,5 +27,5 @@ interface Plugin * * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception (The same as HttpAsyncClient) */ - public function handleRequest(RequestInterface $request, callable $next, callable $first); + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise; } diff --git a/src/Plugin/AddHostPlugin.php b/src/Plugin/AddHostPlugin.php index 050feb1..492667e 100644 --- a/src/Plugin/AddHostPlugin.php +++ b/src/Plugin/AddHostPlugin.php @@ -3,6 +3,7 @@ namespace Http\Client\Common\Plugin; use Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -48,7 +49,7 @@ public function __construct(UriInterface $host, array $config = []) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { if ($this->replace || '' === $request->getUri()->getHost()) { $uri = $request->getUri() diff --git a/src/Plugin/AddPathPlugin.php b/src/Plugin/AddPathPlugin.php index 1675088..ae5d97d 100644 --- a/src/Plugin/AddPathPlugin.php +++ b/src/Plugin/AddPathPlugin.php @@ -3,6 +3,7 @@ namespace Http\Client\Common\Plugin; use Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; @@ -41,7 +42,7 @@ public function __construct(UriInterface $uri) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $identifier = spl_object_hash((object) $first); diff --git a/src/Plugin/AuthenticationPlugin.php b/src/Plugin/AuthenticationPlugin.php index ff33f9b..5b6c447 100644 --- a/src/Plugin/AuthenticationPlugin.php +++ b/src/Plugin/AuthenticationPlugin.php @@ -4,6 +4,7 @@ use Http\Client\Common\Plugin; use Http\Message\Authentication; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; /** @@ -26,7 +27,7 @@ public function __construct(Authentication $authentication) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $request = $this->authentication->authenticate($request); diff --git a/src/Plugin/BaseUriPlugin.php b/src/Plugin/BaseUriPlugin.php index 669c308..ee9e075 100644 --- a/src/Plugin/BaseUriPlugin.php +++ b/src/Plugin/BaseUriPlugin.php @@ -3,6 +3,7 @@ namespace Http\Client\Common\Plugin; use Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; @@ -39,7 +40,7 @@ public function __construct(UriInterface $uri, array $hostConfig = []) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $addHostNext = function (RequestInterface $request) use ($next, $first) { return $this->addHostPlugin->handleRequest($request, $next, $first); diff --git a/src/Plugin/ContentLengthPlugin.php b/src/Plugin/ContentLengthPlugin.php index 0f7aafa..dff2ca8 100644 --- a/src/Plugin/ContentLengthPlugin.php +++ b/src/Plugin/ContentLengthPlugin.php @@ -4,6 +4,7 @@ use Http\Client\Common\Plugin; use Http\Message\Encoding\ChunkStream; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; /** @@ -16,7 +17,7 @@ final class ContentLengthPlugin implements Plugin /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { if (!$request->hasHeader('Content-Length')) { $stream = $request->getBody(); diff --git a/src/Plugin/ContentTypePlugin.php b/src/Plugin/ContentTypePlugin.php index d0bfa8d..190f125 100644 --- a/src/Plugin/ContentTypePlugin.php +++ b/src/Plugin/ContentTypePlugin.php @@ -3,6 +3,7 @@ namespace Http\Client\Common\Plugin; use Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -57,7 +58,7 @@ public function __construct(array $config = []) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { if (!$request->hasHeader('Content-Type')) { $stream = $request->getBody(); @@ -93,10 +94,8 @@ public function handleRequest(RequestInterface $request, callable $next, callabl /** * @param $stream StreamInterface - * - * @return bool */ - private function isJson($stream) + private function isJson($stream): bool { $stream->rewind(); diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index 3ab5388..84669cf 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -8,6 +8,7 @@ use Http\Message\CookieJar; use Http\Message\CookieUtil; use Http\Message\Exception\UnexpectedValueException; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -33,7 +34,7 @@ public function __construct(CookieJar $cookieJar) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $cookies = []; foreach ($this->cookieJar->getCookies() as $cookie) { @@ -99,7 +100,7 @@ private function createCookie(RequestInterface $request, $setCookie) $parts = array_map('trim', explode(';', $setCookie)); if (empty($parts) || !strpos($parts[0], '=')) { - return; + return null; } list($name, $cookieValue) = $this->createValueKey(array_shift($parts)); diff --git a/src/Plugin/DecoderPlugin.php b/src/Plugin/DecoderPlugin.php index 3a28f5b..0930779 100644 --- a/src/Plugin/DecoderPlugin.php +++ b/src/Plugin/DecoderPlugin.php @@ -4,6 +4,7 @@ use Http\Client\Common\Plugin; use Http\Message\Encoding; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; @@ -48,7 +49,7 @@ public function __construct(array $config = []) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $encodings = extension_loaded('zlib') ? ['gzip', 'deflate'] : ['identity']; diff --git a/src/Plugin/ErrorPlugin.php b/src/Plugin/ErrorPlugin.php index 4fd7201..1d25fb7 100644 --- a/src/Plugin/ErrorPlugin.php +++ b/src/Plugin/ErrorPlugin.php @@ -5,6 +5,7 @@ use Http\Client\Common\Exception\ClientErrorException; use Http\Client\Common\Exception\ServerErrorException; use Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -46,7 +47,7 @@ public function __construct(array $config = []) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $promise = $next($request); @@ -66,7 +67,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl * * @return ResponseInterface If status code is not in 4xx or 5xx return response */ - private function transformResponseToException(RequestInterface $request, ResponseInterface $response) + private function transformResponseToException(RequestInterface $request, ResponseInterface $response): ResponseInterface { if (!$this->onlyServerException && $response->getStatusCode() >= 400 && $response->getStatusCode() < 500) { throw new ClientErrorException($response->getReasonPhrase(), $request, $response); diff --git a/src/Plugin/HeaderAppendPlugin.php b/src/Plugin/HeaderAppendPlugin.php index 26fd813..ca44836 100644 --- a/src/Plugin/HeaderAppendPlugin.php +++ b/src/Plugin/HeaderAppendPlugin.php @@ -3,6 +3,7 @@ namespace Http\Client\Common\Plugin; use Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; /** @@ -21,7 +22,7 @@ final class HeaderAppendPlugin implements Plugin /** * @var array */ - private $headers = []; + private $headers; /** * @param array $headers Hashmap of header name to header value @@ -34,7 +35,7 @@ public function __construct(array $headers) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { foreach ($this->headers as $header => $headerValue) { $request = $request->withAddedHeader($header, $headerValue); diff --git a/src/Plugin/HeaderDefaultsPlugin.php b/src/Plugin/HeaderDefaultsPlugin.php index 6dfc111..77ffe8c 100644 --- a/src/Plugin/HeaderDefaultsPlugin.php +++ b/src/Plugin/HeaderDefaultsPlugin.php @@ -3,6 +3,7 @@ namespace Http\Client\Common\Plugin; use Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; /** @@ -30,7 +31,7 @@ public function __construct(array $headers) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { foreach ($this->headers as $header => $headerValue) { if (!$request->hasHeader($header)) { diff --git a/src/Plugin/HeaderRemovePlugin.php b/src/Plugin/HeaderRemovePlugin.php index fc9c19d..0a34248 100644 --- a/src/Plugin/HeaderRemovePlugin.php +++ b/src/Plugin/HeaderRemovePlugin.php @@ -3,6 +3,7 @@ namespace Http\Client\Common\Plugin; use Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; /** @@ -28,7 +29,7 @@ public function __construct(array $headers) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { foreach ($this->headers as $header) { if ($request->hasHeader($header)) { diff --git a/src/Plugin/HeaderSetPlugin.php b/src/Plugin/HeaderSetPlugin.php index 75f11d4..210bb42 100644 --- a/src/Plugin/HeaderSetPlugin.php +++ b/src/Plugin/HeaderSetPlugin.php @@ -3,6 +3,7 @@ namespace Http\Client\Common\Plugin; use Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; /** @@ -17,7 +18,7 @@ final class HeaderSetPlugin implements Plugin /** * @var array */ - private $headers = []; + private $headers; /** * @param array $headers Hashmap of header name to header value @@ -30,7 +31,7 @@ public function __construct(array $headers) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { foreach ($this->headers as $header => $headerValue) { $request = $request->withHeader($header, $headerValue); diff --git a/src/Plugin/HistoryPlugin.php b/src/Plugin/HistoryPlugin.php index 0c59a2a..7b14485 100644 --- a/src/Plugin/HistoryPlugin.php +++ b/src/Plugin/HistoryPlugin.php @@ -4,6 +4,7 @@ use Http\Client\Common\Plugin; use Http\Client\Exception; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -29,7 +30,7 @@ public function __construct(Journal $journal) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $journal = $this->journal; diff --git a/src/Plugin/QueryDefaultsPlugin.php b/src/Plugin/QueryDefaultsPlugin.php index d9c06d6..2693507 100644 --- a/src/Plugin/QueryDefaultsPlugin.php +++ b/src/Plugin/QueryDefaultsPlugin.php @@ -3,6 +3,7 @@ namespace Http\Client\Common\Plugin; use Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; /** @@ -31,7 +32,7 @@ public function __construct(array $queryParams) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $uri = $request->getUri(); diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index d2f442e..80c5251 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -6,6 +6,7 @@ use Http\Client\Common\Exception\MultipleRedirectionException; use Http\Client\Common\Plugin; use Http\Client\Exception\HttpException; +use Http\Promise\Promise; use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -131,7 +132,7 @@ public function __construct(array $config = []) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { // Check in storage if (array_key_exists((string) $request->getUri(), $this->redirectStorage)) { @@ -215,10 +216,8 @@ protected function buildRedirectRequest(RequestInterface $request, UriInterface * * @throws HttpException If location header is not usable (missing or incorrect) * @throws MultipleRedirectionException If a 300 status code is received and default location cannot be resolved (doesn't use the location header or not present) - * - * @return UriInterface */ - private function createUri(ResponseInterface $response, RequestInterface $request) + private function createUri(ResponseInterface $response, RequestInterface $request): UriInterface { if ($this->redirectCodes[$response->getStatusCode()]['multiple'] && (!$this->useDefaultForMultiple || !$response->hasHeader('Location'))) { throw new MultipleRedirectionException('Cannot choose a redirection', $request, $response); diff --git a/src/Plugin/RequestMatcherPlugin.php b/src/Plugin/RequestMatcherPlugin.php index dd97b3c..d7d3f3d 100644 --- a/src/Plugin/RequestMatcherPlugin.php +++ b/src/Plugin/RequestMatcherPlugin.php @@ -4,6 +4,7 @@ use Http\Client\Common\Plugin; use Http\Message\RequestMatcher; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; /** @@ -32,7 +33,7 @@ public function __construct(RequestMatcher $requestMatcher, Plugin $delegatedPlu /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { if ($this->requestMatcher->matches($request)) { return $this->delegatedPlugin->handleRequest($request, $next, $first); diff --git a/src/Plugin/RetryPlugin.php b/src/Plugin/RetryPlugin.php index 96cfbb2..c10bce4 100644 --- a/src/Plugin/RetryPlugin.php +++ b/src/Plugin/RetryPlugin.php @@ -4,6 +4,7 @@ use Http\Client\Common\Plugin; use Http\Client\Exception; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -72,7 +73,7 @@ public function __construct(array $config = []) /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $chainIdentifier = spl_object_hash((object) $first); diff --git a/src/PluginClient.php b/src/PluginClient.php index 6881c93..264c2fe 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -101,10 +101,8 @@ public function sendAsyncRequest(RequestInterface $request) /** * Configure the plugin client. - * - * @return array */ - private function configure(array $options = []) + private function configure(array $options = []): array { $resolver = new OptionsResolver(); $resolver->setDefaults([ @@ -121,10 +119,8 @@ private function configure(array $options = []) * * @param Plugin[] $pluginList A list of plugins * @param callable $clientCallable Callable making the HTTP call - * - * @return callable */ - private function createPluginChain($pluginList, callable $clientCallable) + private function createPluginChain(array $pluginList, callable $clientCallable): callable { $firstCallable = $lastCallable = $clientCallable; diff --git a/src/PluginClientFactory.php b/src/PluginClientFactory.php index a262f80..77642ea 100644 --- a/src/PluginClientFactory.php +++ b/src/PluginClientFactory.php @@ -42,10 +42,8 @@ public static function setFactory(callable $factory) * } * * @see PluginClient constructor for PluginClient specific $options. - * - * @return PluginClient */ - public function createClient($client, array $plugins = [], array $options = []) + public function createClient($client, array $plugins = [], array $options = []): PluginClient { if (static::$factory) { $factory = static::$factory; From 14a2a396652560a3037912915fdeb10239398c72 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Thu, 22 Nov 2018 10:39:42 +0100 Subject: [PATCH 012/123] Update phpcs --- .gitignore | 3 ++- .php_cs | 22 ---------------------- .php_cs.dist | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+), 23 deletions(-) delete mode 100644 .php_cs create mode 100644 .php_cs.dist diff --git a/.gitignore b/.gitignore index 560b927..8ce13f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ +/.php_cs +/.php_cs.cache /behat.yml /build/ /composer.lock /phpspec.yml /phpunit.xml /vendor/ -/.php_cs.cache diff --git a/.php_cs b/.php_cs deleted file mode 100644 index 7039b1e..0000000 --- a/.php_cs +++ /dev/null @@ -1,22 +0,0 @@ -setRules([ - '@PSR2' => true, - '@Symfony' => true, - 'array_syntax' => [ - 'syntax' => 'short', - ], - 'no_empty_phpdoc' => true, - 'no_superfluous_phpdoc_tags' => true, -]); - -$finder = PhpCsFixer\Finder::create(); -$finder->in([ - 'src', - 'spec' -]); - -$config->setFinder($finder); - -return $config; diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..b241e67 --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,23 @@ +in('src') + ->in('spec') +; +return PhpCsFixer\Config::create() + ->setRules([ + '@PSR2' => true, + '@Symfony' => true, + 'array_syntax' => [ + 'syntax' => 'short', + ], + 'no_empty_phpdoc' => true, + 'no_superfluous_phpdoc_tags' => true, + ]) + ->setFinder($finder); From 8616247dbf1a9c1b073b3a62f45055efbd129c97 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Tue, 4 Dec 2018 16:37:28 +0100 Subject: [PATCH 013/123] make redirect plugin final and refactor phpspec test to support that --- spec/Plugin/RedirectPluginSpec.php | 228 +++++++++++++++++------------ src/Plugin/RedirectPlugin.php | 18 +-- 2 files changed, 141 insertions(+), 105 deletions(-) diff --git a/spec/Plugin/RedirectPluginSpec.php b/spec/Plugin/RedirectPluginSpec.php index e24b6b1..de9b30a 100644 --- a/spec/Plugin/RedirectPluginSpec.php +++ b/spec/Plugin/RedirectPluginSpec.php @@ -73,50 +73,19 @@ public function it_redirects_on_302( $finalPromise->wait()->shouldReturn($finalResponse); } - public function it_use_storage_on_301(UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, RequestInterface $modifiedRequest) - { - $this->beAnInstanceOf(RedirectPluginStub::class); - $this->beConstructedWith($uriRedirect, '/original', '301'); - - $next = function () { - throw new \Exception('Must not be called'); - }; - - $request->getUri()->willReturn($uri); - $uri->__toString()->willReturn('/original'); - $request->withUri($uriRedirect)->willReturn($modifiedRequest); - - $modifiedRequest->getUri()->willReturn($uriRedirect); - $modifiedRequest->getMethod()->willReturn('GET'); - - $uriRedirect->__toString()->willReturn('/redirect'); - - $this->handleRequest($request, $next, PluginStub::first()); - } - - public function it_stores_a_301( + public function it_use_storage_on_301( UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, - ResponseInterface $responseRedirect, RequestInterface $modifiedRequest, ResponseInterface $finalResponse, - Promise $promise + ResponseInterface $redirectResponse ) { - $this->beAnInstanceOf(RedirectPluginStub::class); - $this->beConstructedWith($uriRedirect, '', '301'); - $request->getUri()->willReturn($uri); - $uri->__toString()->willReturn('/301-url'); - - $responseRedirect->getStatusCode()->willReturn('301'); - $responseRedirect->hasHeader('Location')->willReturn(true); - $responseRedirect->getHeaderLine('Location')->willReturn('/redirect'); - + $uri->__toString()->willReturn('/original'); $uri->withPath('/redirect')->willReturn($uriRedirect); - $uriRedirect->withFragment('')->willReturn($uriRedirect); $uriRedirect->withQuery('')->willReturn($uriRedirect); - + $uriRedirect->withFragment('')->willReturn($uriRedirect); $request->withUri($uriRedirect)->willReturn($modifiedRequest); $modifiedRequest->getUri()->willReturn($uriRedirect); @@ -124,23 +93,55 @@ public function it_stores_a_301( $uriRedirect->__toString()->willReturn('/redirect'); - $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { - if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { - return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); - } - }; + $finalResponse->getStatusCode()->willReturn(200); - $first = function (RequestInterface $receivedRequest) use ($modifiedRequest, $promise) { - if (Argument::is($modifiedRequest->getWrappedObject())->scoreArgument($receivedRequest)) { - return $promise->getWrappedObject(); + $redirectResponse->getStatusCode()->willReturn(301); + $redirectResponse->hasHeader('Location')->willReturn(true); + $redirectResponse->getHeaderLine('Location')->willReturn('/redirect'); + + $nextCalled = false; + $next = function (RequestInterface $request) use (&$nextCalled, $finalResponse, $redirectResponse): Promise { + switch ($request->getUri()) { + case '/original': + if ($nextCalled) { + throw new \Exception('Must only be called once'); + } + $nextCalled = true; + + return new HttpFulfilledPromise($redirectResponse->getWrappedObject()); + case '/redirect': + + return new HttpFulfilledPromise($finalResponse->getWrappedObject()); + default: + throw new \Exception('Test setup error with request uri '.$request->getUri()); } }; + $first = $this->buildFirst($modifiedRequest, $next); - $promise->getState()->willReturn(Promise::FULFILLED); - $promise->wait()->shouldBeCalled()->willReturn($finalResponse); + $this->handleRequest($request, $next, $first); + // rebuild first as this is expected to be called again + $first = $this->buildFirst($modifiedRequest, $next); + // next should not be called again $this->handleRequest($request, $next, $first); - $this->hasStorage('/301-url')->shouldReturn(true); + } + + private function buildFirst(RequestInterface $modifiedRequest, callable $next): callable + { + $redirectPlugin = $this; + $firstCalled = false; + + return function (RequestInterface $request) use (&$modifiedRequest, $redirectPlugin, $next, &$firstCalled) { + if ($firstCalled) { + throw new \Exception('Only one restart expected'); + } + $firstCalled = true; + if ($modifiedRequest->getWrappedObject() !== $request) { + //throw new \Exception('Redirection failed'); + } + + return $redirectPlugin->getWrappedObject()->handleRequest($request, $next, $this); + }; } public function it_replace_full_url( @@ -359,35 +360,100 @@ public function it_clears_headers( $this->handleRequest($request, $next, $first); } - public function it_throws_circular_redirection_exception(UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, ResponseInterface $responseRedirect, RequestInterface $modifiedRequest) - { - $first = function () {}; + /** + * This is the "redirection does not redirect case. + */ + public function it_throws_circular_redirection_exception_on_redirect_that_does_not_change_url( + UriInterface $redirectUri, + RequestInterface $request, + ResponseInterface $redirectResponse + ) { + $redirectResponse->getStatusCode()->willReturn(302); + $redirectResponse->hasHeader('Location')->willReturn(true); + $redirectResponse->getHeaderLine('Location')->willReturn('/redirect'); - $this->beAnInstanceOf(RedirectPluginStubCircular::class); - $this->beConstructedWith(spl_object_hash((object) $first)); + $next = function () use ($redirectResponse): Promise { + return new HttpFulfilledPromise($redirectResponse->getWrappedObject()); + }; - $request->getUri()->willReturn($uri); - $uri->__toString()->willReturn('/original'); + $first = function () { + throw new \Exception('First should never be called'); + }; - $responseRedirect->getStatusCode()->willReturn('302'); - $responseRedirect->hasHeader('Location')->willReturn(true); - $responseRedirect->getHeaderLine('Location')->willReturn('/redirect'); + $request->getUri()->willReturn($redirectUri); + $redirectUri->__toString()->willReturn('/redirect'); - $uri->withPath('/redirect')->willReturn($uriRedirect); - $uriRedirect->withFragment('')->willReturn($uriRedirect); - $uriRedirect->withQuery('')->willReturn($uriRedirect); + $redirectUri->withPath('/redirect')->willReturn($redirectUri); + $redirectUri->withFragment('')->willReturn($redirectUri); + $redirectUri->withQuery('')->willReturn($redirectUri); - $request->withUri($uriRedirect)->willReturn($modifiedRequest); - $modifiedRequest->getUri()->willReturn($uriRedirect); - $uriRedirect->__toString()->willReturn('/redirect'); - $modifiedRequest->getMethod()->willReturn('GET'); + $request->withUri($redirectUri)->willReturn($request); + $redirectUri->__toString()->willReturn('/redirect'); + $request->getMethod()->willReturn('GET'); - $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { - if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { - return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); + $promise = $this->handleRequest($request, $next, $first); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); + $promise->shouldThrow(CircularRedirectionException::class)->duringWait(); + } + + /** + * This is a redirection flipping back and forth between two paths. + * + * There could be a larger loop but the logic in the plugin stays the same with as many redirects as needed. + */ + public function it_throws_circular_redirection_exception_on_alternating_redirect( + UriInterface $uri, + UriInterface $redirectUri, + RequestInterface $request, + ResponseInterface $redirectResponse1, + ResponseInterface $redirectResponse2, + RequestInterface $modifiedRequest + ) { + $redirectResponse1->getStatusCode()->willReturn(302); + $redirectResponse1->hasHeader('Location')->willReturn(true); + $redirectResponse1->getHeaderLine('Location')->willReturn('/redirect'); + + $redirectResponse2->getStatusCode()->willReturn(302); + $redirectResponse2->hasHeader('Location')->willReturn(true); + $redirectResponse2->getHeaderLine('Location')->willReturn('/original'); + + $next = function (RequestInterface $currentRequest) use ($request, $redirectResponse1, $redirectResponse2): Promise { + return ($currentRequest === $request->getWrappedObject()) + ? new HttpFulfilledPromise($redirectResponse1->getWrappedObject()) + : new HttpFulfilledPromise($redirectResponse2->getWrappedObject()) + ; + }; + + $redirectPlugin = $this; + $firstCalled = false; + $first = function (RequestInterface $request) use (&$firstCalled, $redirectPlugin, $next, &$first) { + if ($firstCalled) { + throw new \Exception('only one redirect expected'); } + $firstCalled = true; + + return $redirectPlugin->getWrappedObject()->handleRequest($request, $next, $first); }; + $request->getUri()->willReturn($uri); + $uri->__toString()->willReturn('/original'); + + $modifiedRequest->getUri()->willReturn($redirectUri); + $redirectUri->__toString()->willReturn('/redirect'); + + $uri->withPath('/redirect')->willReturn($redirectUri); + $redirectUri->withFragment('')->willReturn($redirectUri); + $redirectUri->withQuery('')->willReturn($redirectUri); + + $redirectUri->withPath('/original')->willReturn($uri); + $uri->withFragment('')->willReturn($uri); + $uri->withQuery('')->willReturn($uri); + + $request->withUri($redirectUri)->willReturn($modifiedRequest); + $request->getMethod()->willReturn('GET'); + $modifiedRequest->withUri($uri)->willReturn($request); + $modifiedRequest->getMethod()->willReturn('GET'); + $promise = $this->handleRequest($request, $next, $first); $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); $promise->shouldThrow(CircularRedirectionException::class)->duringWait(); @@ -440,33 +506,3 @@ public function it_redirects_http_to_https( $finalPromise->wait()->shouldReturn($finalResponse); } } - -class RedirectPluginStub extends RedirectPlugin -{ - public function __construct(UriInterface $uri, $storedUrl, $status, array $config = []) - { - parent::__construct($config); - - $this->redirectStorage[$storedUrl] = [ - 'uri' => $uri, - 'status' => $status, - ]; - } - - public function hasStorage($url) - { - return isset($this->redirectStorage[$url]); - } -} - -class RedirectPluginStubCircular extends RedirectPlugin -{ - public function __construct($chainHash) - { - $this->circularDetection = [ - $chainHash => [ - '/redirect', - ], - ]; - } -} diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index 80c5251..89b0f39 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -18,14 +18,14 @@ * * @author Joel Wurtz */ -class RedirectPlugin implements Plugin +final class RedirectPlugin implements Plugin { /** * Rule on how to redirect, change method for the new request. * * @var array */ - protected $redirectCodes = [ + private $redirectCodes = [ 300 => [ 'switch' => [ 'unless' => ['GET', 'HEAD'], @@ -79,26 +79,26 @@ class RedirectPlugin implements Plugin * false will ditch all previous headers * string[] will keep only headers with the specified names */ - protected $preserveHeader; + private $preserveHeader; /** * Store all previous redirect from 301 / 308 status code. * * @var array */ - protected $redirectStorage = []; + private $redirectStorage = []; /** * Whether the location header must be directly used for a multiple redirection status code (300). * * @var bool */ - protected $useDefaultForMultiple; + private $useDefaultForMultiple; /** * @var array */ - protected $circularDetection = []; + private $circularDetection = []; /** * @param array $config { @@ -143,7 +143,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl return $first($redirectRequest); } - return $next($request)->then(function (ResponseInterface $response) use ($request, $first) { + return $next($request)->then(function (ResponseInterface $response) use ($request, $first): ResponseInterface { $statusCode = $response->getStatusCode(); if (!array_key_exists($statusCode, $this->redirectCodes)) { @@ -171,7 +171,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl ]; } - // Call redirect request in synchrone + // Call redirect request synchronously $redirectPromise = $first($redirectRequest); return $redirectPromise->wait(); @@ -187,7 +187,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl * * @return MessageInterface|RequestInterface */ - protected function buildRedirectRequest(RequestInterface $request, UriInterface $uri, $statusCode) + private function buildRedirectRequest(RequestInterface $request, UriInterface $uri, $statusCode) { $request = $request->withUri($uri); From 18e36cccd8e000b6afb21d9e2593da10bedc5e21 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sun, 9 Dec 2018 12:09:33 +0100 Subject: [PATCH 014/123] adjust decider to not retry client errors (#125) --- CHANGELOG.md | 1 + spec/Plugin/RetryPluginSpec.php | 22 ++++++++++++++++++++++ src/Plugin/RetryPlugin.php | 6 ++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6a01d9..ac296f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 2.0 (unreleased) ### Changed +- RetryPlugin will no longer retry requests when the response failed with a HTTP code < 500. - Abstract method `HttpClientPool::chooseHttpClient()` has now an explicit return type (`Http\Client\Common\HttpClientPoolItem`) - Interface method `Plugin::handleRequest(...)` has now an explicit return type (`Http\Promise\Promise`) diff --git a/spec/Plugin/RetryPluginSpec.php b/spec/Plugin/RetryPluginSpec.php index 682b892..1ffb70a 100644 --- a/spec/Plugin/RetryPluginSpec.php +++ b/spec/Plugin/RetryPluginSpec.php @@ -59,6 +59,28 @@ public function it_throws_exception_on_multiple_exceptions(RequestInterface $req $promise->shouldThrow($exception2)->duringWait(); } + public function it_does_not_retry_client_errors(RequestInterface $request, ResponseInterface $response) + { + $exception = new Exception\HttpException('Exception', $request->getWrappedObject(), $response->getWrappedObject()); + + $seen = false; + $next = function (RequestInterface $receivedRequest) use ($request, $exception, &$seen) { + if (!Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { + throw new \Exception('Unexpected request received'); + } + if ($seen) { + throw new \Exception('This should only be called once'); + } + $seen = true; + + return new HttpRejectedPromise($exception); + }; + + $promise = $this->handleRequest($request, $next, function () {}); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); + $promise->shouldThrow($exception)->duringWait(); + } + public function it_returns_response_on_second_try(RequestInterface $request, ResponseInterface $response) { $exception = new Exception\NetworkException('Exception 1', $request->getWrappedObject()); diff --git a/src/Plugin/RetryPlugin.php b/src/Plugin/RetryPlugin.php index c10bce4..a0da75c 100644 --- a/src/Plugin/RetryPlugin.php +++ b/src/Plugin/RetryPlugin.php @@ -4,6 +4,7 @@ use Http\Client\Common\Plugin; use Http\Client\Exception; +use Http\Client\Exception\HttpException; use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -56,7 +57,8 @@ public function __construct(array $config = []) $resolver->setDefaults([ 'retries' => 1, 'decider' => function (RequestInterface $request, Exception $e) { - return true; + // do not retry client errors + return !$e instanceof HttpException || $e->getCode() >= 500; }, 'delay' => __CLASS__.'::defaultDelay', ]); @@ -101,7 +103,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl $time = call_user_func($this->delay, $request, $exception, $this->retryStorage[$chainIdentifier]); usleep($time); - // Retry in synchrone + // Retry synchronously ++$this->retryStorage[$chainIdentifier]; $promise = $this->handleRequest($request, $next, $first); From 5541efdc6288a40ef1897af6ceef8371137beb92 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sat, 15 Dec 2018 11:28:33 +0100 Subject: [PATCH 015/123] fix AddPathPluginSpec for strict return types --- spec/Plugin/AddPathPluginSpec.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Plugin/AddPathPluginSpec.php b/spec/Plugin/AddPathPluginSpec.php index b2a6cae..414fa04 100644 --- a/spec/Plugin/AddPathPluginSpec.php +++ b/spec/Plugin/AddPathPluginSpec.php @@ -63,7 +63,7 @@ function it_removes_ending_slashes( $uri->getPath()->shouldBeCalled()->willReturn('/users'); $this->beConstructedWith($host); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } public function it_throws_exception_on_empty_path(UriInterface $host) From fadb51abcecdcb0d24ffc2b539f31f030dae1d20 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sun, 2 Dec 2018 09:11:06 +0100 Subject: [PATCH 016/123] mark classes as final and methods and properties as private We add interfaces for final classes so that they can be mocked in testing. We chose to use the Interface suffix here to avoid name clashes, rather than renaming the existing PHP classes. The interfaces are only relevant for unit testing, we do not expect people to provide their own implementations. HttpClientPoolItem is purely internal and we therefore don't want an interface for it. It is marked with @final and explained to not be meant to extend. --- CHANGELOG.md | 4 + .../HttpClientPoolItemSpec.php | 2 +- .../LeastUsedClientPoolSpec.php | 2 +- spec/HttpClientPool/RandomClientPoolSpec.php | 2 +- .../RoundRobinClientPoolSpec.php | 2 +- spec/HttpClientRouterSpec.php | 8 +- spec/HttpMethodsClientSpec.php | 123 ++++++------------ spec/Plugin/AddPathPluginSpec.php | 2 +- src/BatchClient.php | 26 +--- src/BatchClientInterface.php | 34 +++++ src/Deferred.php | 2 +- src/EmulatedHttpAsyncClient.php | 6 +- src/EmulatedHttpClient.php | 6 +- src/HttpClientPool.php | 45 +------ src/HttpClientPool/HttpClientPool.php | 61 +++++++++ .../HttpClientPoolItem.php | 57 ++++---- src/HttpClientPool/LeastUsedClientPool.php | 2 - src/HttpClientPool/RandomClientPool.php | 2 - src/HttpClientPool/RoundRobinClientPool.php | 2 - src/HttpClientRouter.php | 12 +- src/HttpClientRouterInterface.php | 24 ++++ src/HttpMethodsClient.php | 94 +------------ src/HttpMethodsClientInterface.php | 114 ++++++++++++++++ 23 files changed, 326 insertions(+), 306 deletions(-) rename spec/{ => HttpClientPool}/HttpClientPoolItemSpec.php (99%) create mode 100644 src/BatchClientInterface.php create mode 100644 src/HttpClientPool/HttpClientPool.php rename src/{ => HttpClientPool}/HttpClientPoolItem.php (73%) create mode 100644 src/HttpClientRouterInterface.php create mode 100644 src/HttpMethodsClientInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cbc9e0..f115d39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - RetryPlugin will no longer retry requests when the response failed with a HTTP code < 500. - Abstract method `HttpClientPool::chooseHttpClient()` has now an explicit return type (`Http\Client\Common\HttpClientPoolItem`) - Interface method `Plugin::handleRequest(...)` has now an explicit return type (`Http\Promise\Promise`) +- Made classes final that are not intended to be extended. + Added interfaces for BatchClient, HttpClientRouter and HttpMethodsClient. + (These interfaces use the `Interface` suffix to avoid name collisions.) +- Added an interface for HttpClientPool and moved the abstract class to the HttpClientPool sub namespace. ### Removed - Deprecated option `debug_plugins` has been removed from `PluginClient` diff --git a/spec/HttpClientPoolItemSpec.php b/spec/HttpClientPool/HttpClientPoolItemSpec.php similarity index 99% rename from spec/HttpClientPoolItemSpec.php rename to spec/HttpClientPool/HttpClientPoolItemSpec.php index f44351b..22c0b7d 100644 --- a/spec/HttpClientPoolItemSpec.php +++ b/spec/HttpClientPool/HttpClientPoolItemSpec.php @@ -1,6 +1,6 @@ shouldHaveType(HttpClientRouter::class); } + public function it_is_an_http_client_router() + { + $this->shouldImplement(HttpClientRouterInterface::class); + } + public function it_is_an_http_client() { $this->shouldImplement(HttpClient::class); diff --git a/spec/HttpMethodsClientSpec.php b/spec/HttpMethodsClientSpec.php index 74e0810..68e124d 100644 --- a/spec/HttpMethodsClientSpec.php +++ b/spec/HttpMethodsClientSpec.php @@ -2,135 +2,88 @@ namespace spec\Http\Client\Common; -use GuzzleHttp\Psr7\Response; use Http\Client\Common\HttpMethodsClient; use Http\Client\HttpClient; -use Http\Message\MessageFactory; +use Http\Message\RequestFactory; use PhpSpec\ObjectBehavior; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; class HttpMethodsClientSpec extends ObjectBehavior { - public function let(HttpClient $client, MessageFactory $messageFactory) + private static $requestData = [ + 'uri' => '/uri', + 'headers' => [ + 'Content-Type' => 'text/plain', + ], + 'body' => 'body', + ]; + + public function let(HttpClient $client, RequestFactory $requestFactory) { $this->beAnInstanceOf( - HttpMethodsClientStub::class, [ + HttpMethodsClient::class, [ $client, - $messageFactory, + $requestFactory, ] ); } - public function it_sends_a_get_request() + public function it_sends_a_get_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) { - $data = HttpMethodsClientStub::$requestData; - - $this->get($data['uri'], $data['headers'])->shouldReturnAnInstanceOf(ResponseInterface::class); + $this->assert($client, $requestFactory, $request, $response, 'get'); } - public function it_sends_a_head_request() + public function it_sends_a_head_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) { - $data = HttpMethodsClientStub::$requestData; - - $this->head($data['uri'], $data['headers'])->shouldReturnAnInstanceOf(ResponseInterface::class); + $this->assert($client, $requestFactory, $request, $response, 'head'); } - public function it_sends_a_trace_request() + public function it_sends_a_trace_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) { - $data = HttpMethodsClientStub::$requestData; - - $this->trace($data['uri'], $data['headers'])->shouldReturnAnInstanceOf(ResponseInterface::class); + $this->assert($client, $requestFactory, $request, $response, 'trace'); } - public function it_sends_a_post_request() + public function it_sends_a_post_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) { - $data = HttpMethodsClientStub::$requestData; - - $this->post($data['uri'], $data['headers'], $data['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); + $this->assert($client, $requestFactory, $request, $response, 'post', self::$requestData['body']); } - public function it_sends_a_put_request() + public function it_sends_a_put_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) { - $data = HttpMethodsClientStub::$requestData; - - $this->put($data['uri'], $data['headers'], $data['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); + $this->assert($client, $requestFactory, $request, $response, 'put', self::$requestData['body']); } - public function it_sends_a_patch_request() + public function it_sends_a_patch_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) { - $data = HttpMethodsClientStub::$requestData; - - $this->patch($data['uri'], $data['headers'], $data['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); + $this->assert($client, $requestFactory, $request, $response, 'patch', self::$requestData['body']); } - public function it_sends_a_delete_request() + public function it_sends_a_delete_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) { - $data = HttpMethodsClientStub::$requestData; - - $this->delete($data['uri'], $data['headers'], $data['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); + $this->assert($client, $requestFactory, $request, $response, 'delete', self::$requestData['body']); } - public function it_sends_a_options_request() + public function it_sends_an_options_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) { - $data = HttpMethodsClientStub::$requestData; - - $this->options($data['uri'], $data['headers'], $data['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); + $this->assert($client, $requestFactory, $request, $response, 'options', self::$requestData['body']); } - public function it_sends_request_with_underlying_client(HttpClient $client, MessageFactory $messageFactory, RequestInterface $request, ResponseInterface $response) + /** + * Run the actual test. + * + * As there is no data provider in phpspec, we keep separate methods to get new mocks for each test. + */ + private function assert(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response, string $method, string $body = null) { $client->sendRequest($request)->shouldBeCalled()->willReturn($response); + $this->mockFactory($requestFactory, $request, strtoupper($method), $body); - $this->beConstructedWith($client, $messageFactory); - $this->sendRequest($request)->shouldReturn($response); + $this->$method(self::$requestData['uri'], self::$requestData['headers'], self::$requestData['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); } -} - -class HttpMethodsClientStub extends HttpMethodsClient -{ - public static $requestData = [ - 'uri' => '/uri', - 'headers' => [ - 'Content-Type' => 'text/plain', - ], - 'body' => 'body', - ]; - /** - * {@inheritdoc} - */ - public function send($method, $uri, array $headers = [], $body = null): ResponseInterface + private function mockFactory(RequestFactory $requestFactory, RequestInterface $request, string $method, string $body = null) { - if ($uri !== self::$requestData['uri']) { - throw new \InvalidArgumentException('Invalid URI: '.$uri); - } - - if ($headers !== self::$requestData['headers']) { - throw new \InvalidArgumentException('Invalid headers: '.print_r($headers, true)); - } - - switch ($method) { - case 'GET': - case 'HEAD': - case 'TRACE': - if (null !== $body) { - throw new \InvalidArgumentException('Non-empty body'); - } - - return new Response(); - case 'POST': - case 'PUT': - case 'PATCH': - case 'DELETE': - case 'OPTIONS': - if ($body !== self::$requestData['body']) { - throw new \InvalidArgumentException('Invalid body: '.print_r($body, true)); - } - - return new Response(); - default: - throw new \InvalidArgumentException('Invalid method: '.$method); - } + $requestFactory->createRequest($method, self::$requestData['uri'], self::$requestData['headers'], $body)->willReturn($request); } } diff --git a/spec/Plugin/AddPathPluginSpec.php b/spec/Plugin/AddPathPluginSpec.php index 414fa04..6491069 100644 --- a/spec/Plugin/AddPathPluginSpec.php +++ b/spec/Plugin/AddPathPluginSpec.php @@ -46,7 +46,7 @@ public function it_adds_path( $this->handleRequest($request, PluginStub::next(), function () {}); } - function it_removes_ending_slashes( + public function it_removes_ending_slashes( RequestInterface $request, UriInterface $host, UriInterface $host2, diff --git a/src/BatchClient.php b/src/BatchClient.php index 8dfeb81..09a83ff 100644 --- a/src/BatchClient.php +++ b/src/BatchClient.php @@ -8,14 +8,7 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -/** - * BatchClient allow to sends multiple request and retrieve a Batch Result. - * - * This implementation simply loops over the requests and uses sendRequest with each of them. - * - * @author Joel Wurtz - */ -class BatchClient implements HttpClient +final class BatchClient implements BatchClientInterface { /** * @var HttpClient @@ -27,28 +20,11 @@ public function __construct(HttpClient $client) $this->client = $client; } - /** - * {@inheritdoc} - */ public function sendRequest(RequestInterface $request): ResponseInterface { return $this->client->sendRequest($request); } - /** - * Send several requests. - * - * You may not assume that the requests are executed in a particular order. If the order matters - * for your application, use sendRequest sequentially. - * - * @param RequestInterface[] The requests to send - * - * @return BatchResult Containing one result per request - * - * @throws BatchException If one or more requests fails. The exception gives access to the - * BatchResult with a map of request to result for success, request to - * exception for failures - */ public function sendRequests(array $requests): BatchResult { $batchResult = new BatchResult(); diff --git a/src/BatchClientInterface.php b/src/BatchClientInterface.php new file mode 100644 index 0000000..ce6c3c7 --- /dev/null +++ b/src/BatchClientInterface.php @@ -0,0 +1,34 @@ + + */ +interface BatchClientInterface extends HttpClient +{ + /** + * Send several requests. + * + * You may not assume that the requests are executed in a particular order. If the order matters + * for your application, use sendRequest sequentially. + * + * @param RequestInterface[] The requests to send + * + * @return BatchResult Containing one result per request + * + * @throws BatchException If one or more requests fails. The exception gives access to the + * BatchResult with a map of request to result for success, request to + * exception for failures + */ + public function sendRequests(array $requests): BatchResult; +} diff --git a/src/Deferred.php b/src/Deferred.php index 7413451..c294a8d 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -9,7 +9,7 @@ /** * A deferred allow to return a promise which has not been resolved yet. */ -class Deferred implements Promise +final class Deferred implements Promise { private $value; diff --git a/src/EmulatedHttpAsyncClient.php b/src/EmulatedHttpAsyncClient.php index 64acd59..9d6c557 100644 --- a/src/EmulatedHttpAsyncClient.php +++ b/src/EmulatedHttpAsyncClient.php @@ -6,13 +6,11 @@ use Http\Client\HttpClient; /** - * Emulates an async HTTP client. - * - * This should be replaced by an anonymous class in PHP 7. + * Emulates an async HTTP client with the help of a synchronous client. * * @author Márk Sági-Kazár */ -class EmulatedHttpAsyncClient implements HttpClient, HttpAsyncClient +final class EmulatedHttpAsyncClient implements HttpClient, HttpAsyncClient { use HttpAsyncClientEmulator; use HttpClientDecorator; diff --git a/src/EmulatedHttpClient.php b/src/EmulatedHttpClient.php index 3635e9f..d99b4e4 100644 --- a/src/EmulatedHttpClient.php +++ b/src/EmulatedHttpClient.php @@ -6,13 +6,11 @@ use Http\Client\HttpClient; /** - * Emulates an HTTP client. - * - * This should be replaced by an anonymous class in PHP 7. + * Emulates a synchronous HTTP client with the help of an asynchronous client. * * @author Márk Sági-Kazár */ -class EmulatedHttpClient implements HttpClient, HttpAsyncClient +final class EmulatedHttpClient implements HttpClient, HttpAsyncClient { use HttpAsyncClientDecorator; use HttpClientEmulator; diff --git a/src/HttpClientPool.php b/src/HttpClientPool.php index 90a8464..a730f50 100644 --- a/src/HttpClientPool.php +++ b/src/HttpClientPool.php @@ -2,59 +2,20 @@ namespace Http\Client\Common; -use Http\Client\Common\Exception\HttpClientNotFoundException; +use Http\Client\Common\HttpClientPool\HttpClientPoolItem; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; /** * A http client pool allows to send requests on a pool of different http client using a specific strategy (least used, * round robin, ...). */ -abstract class HttpClientPool implements HttpAsyncClient, HttpClient +interface HttpClientPool extends HttpAsyncClient, HttpClient { - /** - * @var HttpClientPoolItem[] - */ - protected $clientPool = []; - /** * Add a client to the pool. * * @param HttpClient|HttpAsyncClient|HttpClientPoolItem $client */ - public function addHttpClient($client) - { - if (!$client instanceof HttpClientPoolItem) { - $client = new HttpClientPoolItem($client); - } - - $this->clientPool[] = $client; - } - - /** - * Return an http client given a specific strategy. - * - * @throws HttpClientNotFoundException When no http client has been found into the pool - * - * @return HttpClientPoolItem Return a http client that can do both sync or async - */ - abstract protected function chooseHttpClient(): HttpClientPoolItem; - - /** - * {@inheritdoc} - */ - public function sendAsyncRequest(RequestInterface $request) - { - return $this->chooseHttpClient()->sendAsyncRequest($request); - } - - /** - * {@inheritdoc} - */ - public function sendRequest(RequestInterface $request): ResponseInterface - { - return $this->chooseHttpClient()->sendRequest($request); - } + public function addHttpClient($client); } diff --git a/src/HttpClientPool/HttpClientPool.php b/src/HttpClientPool/HttpClientPool.php new file mode 100644 index 0000000..cbac805 --- /dev/null +++ b/src/HttpClientPool/HttpClientPool.php @@ -0,0 +1,61 @@ +clientPool[] = $client; + } + + /** + * Return an http client given a specific strategy. + * + * @throws HttpClientNotFoundException When no http client has been found into the pool + * + * @return HttpClientPoolItem Return a http client that can do both sync or async + */ + abstract protected function chooseHttpClient(): HttpClientPoolItem; + + /** + * {@inheritdoc} + */ + public function sendAsyncRequest(RequestInterface $request) + { + return $this->chooseHttpClient()->sendAsyncRequest($request); + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + return $this->chooseHttpClient()->sendRequest($request); + } +} diff --git a/src/HttpClientPoolItem.php b/src/HttpClientPool/HttpClientPoolItem.php similarity index 73% rename from src/HttpClientPoolItem.php rename to src/HttpClientPool/HttpClientPoolItem.php index a46ee2f..4094502 100644 --- a/src/HttpClientPoolItem.php +++ b/src/HttpClientPool/HttpClientPoolItem.php @@ -1,7 +1,8 @@ */ @@ -29,7 +37,11 @@ class HttpClientPoolItem implements HttpClient, HttpAsyncClient private $disabledAt; /** - * @var int|null Number of seconds after this client is reenable, by default null: never reenable this client + * Number of seconds until this client is enabled again after an error. + * + * null: never reenable this client. + * + * @var int|null */ private $reenableAfter; @@ -40,7 +52,7 @@ class HttpClientPoolItem implements HttpClient, HttpAsyncClient /** * @param HttpClient|HttpAsyncClient $client - * @param null|int $reenableAfter Number of seconds after this client is reenable + * @param null|int $reenableAfter Number of seconds until this client is enabled again after an error */ public function __construct($client, $reenableAfter = null) { @@ -97,21 +109,16 @@ public function sendAsyncRequest(RequestInterface $request) /** * Whether this client is disabled or not. * - * Will also reactivate this client if possible - * - * @internal - * - * @return bool + * If the client was disabled, calling this method checks if the client can + * be reenabled and if so enables it. */ - public function isDisabled() + public function isDisabled(): bool { - $disabledAt = $this->getDisabledAt(); - - if (null !== $this->reenableAfter && null !== $disabledAt) { + if (null !== $this->reenableAfter && null !== $this->disabledAt) { // Reenable after a certain time $now = new \DateTime(); - if (($now->getTimestamp() - $disabledAt->getTimestamp()) >= $this->reenableAfter) { + if (($now->getTimestamp() - $this->disabledAt->getTimestamp()) >= $this->reenableAfter) { $this->enable(); return false; @@ -120,31 +127,17 @@ public function isDisabled() return true; } - return null !== $disabledAt; + return null !== $this->disabledAt; } /** - * Get current number of request that is send by the underlying http client. - * - * @internal - * - * @return int + * Get current number of request that are currently being sent by the underlying HTTP client. */ - public function getSendingRequestCount() + public function getSendingRequestCount(): int { return $this->sendingRequestCount; } - /** - * Return when this client has been disabled or null if it's enabled. - * - * @return \DateTime|null - */ - private function getDisabledAt() - { - return $this->disabledAt; - } - /** * Increment the request count. */ diff --git a/src/HttpClientPool/LeastUsedClientPool.php b/src/HttpClientPool/LeastUsedClientPool.php index 61eb670..e3c29b7 100644 --- a/src/HttpClientPool/LeastUsedClientPool.php +++ b/src/HttpClientPool/LeastUsedClientPool.php @@ -3,8 +3,6 @@ namespace Http\Client\Common\HttpClientPool; use Http\Client\Common\Exception\HttpClientNotFoundException; -use Http\Client\Common\HttpClientPool; -use Http\Client\Common\HttpClientPoolItem; /** * LeastUsedClientPool will choose the client with the less current request in the pool. diff --git a/src/HttpClientPool/RandomClientPool.php b/src/HttpClientPool/RandomClientPool.php index 2889ed7..3adf5fd 100644 --- a/src/HttpClientPool/RandomClientPool.php +++ b/src/HttpClientPool/RandomClientPool.php @@ -3,8 +3,6 @@ namespace Http\Client\Common\HttpClientPool; use Http\Client\Common\Exception\HttpClientNotFoundException; -use Http\Client\Common\HttpClientPool; -use Http\Client\Common\HttpClientPoolItem; /** * RoundRobinClientPool will choose the next client in the pool. diff --git a/src/HttpClientPool/RoundRobinClientPool.php b/src/HttpClientPool/RoundRobinClientPool.php index 3315b51..13cdf13 100644 --- a/src/HttpClientPool/RoundRobinClientPool.php +++ b/src/HttpClientPool/RoundRobinClientPool.php @@ -3,8 +3,6 @@ namespace Http\Client\Common\HttpClientPool; use Http\Client\Common\Exception\HttpClientNotFoundException; -use Http\Client\Common\HttpClientPool; -use Http\Client\Common\HttpClientPoolItem; /** * RoundRobinClientPool will choose the next client in the pool. diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index 00ca15f..56be0bf 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -10,11 +10,11 @@ use Psr\Http\Message\ResponseInterface; /** - * Route a request to a specific client in the stack based using a RequestMatcher. + * {@inheritdoc} * * @author Joel Wurtz */ -final class HttpClientRouter implements HttpClient, HttpAsyncClient +final class HttpClientRouter implements HttpClientRouterInterface { /** * @var array @@ -26,9 +26,7 @@ final class HttpClientRouter implements HttpClient, HttpAsyncClient */ public function sendRequest(RequestInterface $request): ResponseInterface { - $client = $this->chooseHttpClient($request); - - return $client->sendRequest($request); + return $this->chooseHttpClient($request)->sendRequest($request); } /** @@ -36,9 +34,7 @@ public function sendRequest(RequestInterface $request): ResponseInterface */ public function sendAsyncRequest(RequestInterface $request) { - $client = $this->chooseHttpClient($request); - - return $client->sendAsyncRequest($request); + return $this->chooseHttpClient($request)->sendAsyncRequest($request); } /** diff --git a/src/HttpClientRouterInterface.php b/src/HttpClientRouterInterface.php new file mode 100644 index 0000000..67fb822 --- /dev/null +++ b/src/HttpClientRouterInterface.php @@ -0,0 +1,24 @@ + + */ +interface HttpClientRouterInterface extends HttpClient, HttpAsyncClient +{ + /** + * Add a client to the router. + * + * @param HttpClient|HttpAsyncClient $client + */ + public function addClient($client, RequestMatcher $requestMatcher); +} diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index bc02ef8..69e4df9 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -2,29 +2,12 @@ namespace Http\Client\Common; -use Http\Client\Exception; use Http\Client\HttpClient; use Http\Message\RequestFactory; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamInterface; -use Psr\Http\Message\UriInterface; -/** - * Convenience HTTP client that integrates the MessageFactory in order to send - * requests in the following form:. - * - * $client - * ->get('/foo') - * ->post('/bar') - * ; - * - * The client also exposes the sendRequest methods of the wrapped HttpClient. - * - * @author Márk Sági-Kazár - * @author David Buchmann - */ -class HttpMethodsClient implements HttpClient +final class HttpMethodsClient implements HttpMethodsClientInterface { /** * @var HttpClient @@ -46,116 +29,46 @@ public function __construct(HttpClient $httpClient, RequestFactory $requestFacto $this->requestFactory = $requestFactory; } - /** - * Sends a GET request. - * - * @param string|UriInterface $uri - * - * @throws Exception - */ public function get($uri, array $headers = []): ResponseInterface { return $this->send('GET', $uri, $headers, null); } - /** - * Sends an HEAD request. - * - * @param string|UriInterface $uri - * - * @throws Exception - */ public function head($uri, array $headers = []): ResponseInterface { return $this->send('HEAD', $uri, $headers, null); } - /** - * Sends a TRACE request. - * - * @param string|UriInterface $uri - * - * @throws Exception - */ public function trace($uri, array $headers = []): ResponseInterface { return $this->send('TRACE', $uri, $headers, null); } - /** - * Sends a POST request. - * - * @param string|UriInterface $uri - * @param string|StreamInterface|null $body - * - * @throws Exception - */ public function post($uri, array $headers = [], $body = null): ResponseInterface { return $this->send('POST', $uri, $headers, $body); } - /** - * Sends a PUT request. - * - * @param string|UriInterface $uri - * @param string|StreamInterface|null $body - * - * @throws Exception - */ public function put($uri, array $headers = [], $body = null): ResponseInterface { return $this->send('PUT', $uri, $headers, $body); } - /** - * Sends a PATCH request. - * - * @param string|UriInterface $uri - * @param string|StreamInterface|null $body - * - * @throws Exception - */ public function patch($uri, array $headers = [], $body = null): ResponseInterface { return $this->send('PATCH', $uri, $headers, $body); } - /** - * Sends a DELETE request. - * - * @param string|UriInterface $uri - * @param string|StreamInterface|null $body - * - * @throws Exception - */ public function delete($uri, array $headers = [], $body = null): ResponseInterface { return $this->send('DELETE', $uri, $headers, $body); } - /** - * Sends an OPTIONS request. - * - * @param string|UriInterface $uri - * @param string|StreamInterface|null $body - * - * @throws Exception - */ public function options($uri, array $headers = [], $body = null): ResponseInterface { return $this->send('OPTIONS', $uri, $headers, $body); } - /** - * Sends a request with any HTTP method. - * - * @param string $method HTTP method to use - * @param string|UriInterface $uri - * @param string|StreamInterface|null $body - * - * @throws Exception - */ public function send($method, $uri, array $headers = [], $body = null): ResponseInterface { return $this->sendRequest($this->requestFactory->createRequest( @@ -166,11 +79,6 @@ public function send($method, $uri, array $headers = [], $body = null): Response )); } - /** - * Forward to the underlying HttpClient. - * - * {@inheritdoc} - */ public function sendRequest(RequestInterface $request): ResponseInterface { return $this->httpClient->sendRequest($request); diff --git a/src/HttpMethodsClientInterface.php b/src/HttpMethodsClientInterface.php new file mode 100644 index 0000000..6bc409f --- /dev/null +++ b/src/HttpMethodsClientInterface.php @@ -0,0 +1,114 @@ +get('/foo') + * ->post('/bar') + * ; + * + * The client also exposes the sendRequest methods of the wrapped HttpClient. + * + * @author Márk Sági-Kazár + * @author David Buchmann + */ +interface HttpMethodsClientInterface extends HttpClient +{ + /** + * Sends a GET request. + * + * @param string|UriInterface $uri + * + * @throws Exception + */ + public function get($uri, array $headers = []): ResponseInterface; + + /** + * Sends an HEAD request. + * + * @param string|UriInterface $uri + * + * @throws Exception + */ + public function head($uri, array $headers = []): ResponseInterface; + + /** + * Sends a TRACE request. + * + * @param string|UriInterface $uri + * + * @throws Exception + */ + public function trace($uri, array $headers = []): ResponseInterface; + + /** + * Sends a POST request. + * + * @param string|UriInterface $uri + * @param string|StreamInterface|null $body + * + * @throws Exception + */ + public function post($uri, array $headers = [], $body = null): ResponseInterface; + + /** + * Sends a PUT request. + * + * @param string|UriInterface $uri + * @param string|StreamInterface|null $body + * + * @throws Exception + */ + public function put($uri, array $headers = [], $body = null): ResponseInterface; + + /** + * Sends a PATCH request. + * + * @param string|UriInterface $uri + * @param string|StreamInterface|null $body + * + * @throws Exception + */ + public function patch($uri, array $headers = [], $body = null): ResponseInterface; + + /** + * Sends a DELETE request. + * + * @param string|UriInterface $uri + * @param string|StreamInterface|null $body + * + * @throws Exception + */ + public function delete($uri, array $headers = [], $body = null): ResponseInterface; + + /** + * Sends an OPTIONS request. + * + * @param string|UriInterface $uri + * @param string|StreamInterface|null $body + * + * @throws Exception + */ + public function options($uri, array $headers = [], $body = null): ResponseInterface; + + /** + * Sends a request with any HTTP method. + * + * @param string $method HTTP method to use + * @param string|UriInterface $uri + * @param string|StreamInterface|null $body + * + * @throws Exception + */ + public function send($method, $uri, array $headers = [], $body = null): ResponseInterface; +} From db9afa6af5199af6f0f6fc8cf3b8794235000824 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Wed, 26 Dec 2018 09:42:32 +0100 Subject: [PATCH 017/123] Remove php-cs-fixer from dependencies --- composer.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 0b32bc2..7ada728 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,7 @@ "guzzlehttp/psr7": "^1.4", "phpspec/phpspec": "^3.4 || ^4.2", "phpspec/prophecy": ">=1.8", - "sebastian/comparator": ">=2", - "friendsofphp/php-cs-fixer": "^2.2" + "sebastian/comparator": ">=2" }, "suggest": { "php-http/logger-plugin": "PSR-3 Logger plugin", @@ -41,8 +40,6 @@ } }, "scripts": { - "cs-check": "vendor/bin/php-cs-fixer fix --dry-run", - "cs-fix": "vendor/bin/php-cs-fixer fix", "test": "vendor/bin/phpspec run", "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" }, From 3e3eed0be5c9ff102a8dce8c92f4e2771996a66b Mon Sep 17 00:00:00 2001 From: Nyholm Date: Wed, 26 Dec 2018 09:40:02 +0100 Subject: [PATCH 018/123] Test on PHP 7.3 --- .travis.yml | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 796c813..84eac28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,23 +5,14 @@ cache: directories: - $HOME/.composer/cache/files -env: - global: - - TEST_COMMAND="composer test" - -branches: - except: - - /^analysis-.*$/ - php: - 7.0 - 7.1 - 7.2 +- 7.3 matrix: fast_finish: true - allow_failures: - - php: 7.3 jobs: include: @@ -35,19 +26,16 @@ jobs: - wget https://scrutinizer-ci.com/ocular.phar - php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml --revision=$TRAVIS_COMMIT # Test LTS versions - - php: 7.1 + - php: 7.2 env: DEPENDENCIES="dunglas/symfony-lock:^2" - - php: 7.1 + - php: 7.2 env: DEPENDENCIES="dunglas/symfony-lock:^3" - - php: 7.1 - env: DEPENDENCIES="dunglas/symfony-lock:^4" STABILITY="rc" + - php: 7.2 + env: DEPENDENCIES="dunglas/symfony-lock:^4" # Latest dev release - php: 7.3 - sudo: required - before_install: - - composer remove --dev friendsofphp/php-cs-fixer - - env: STABILITY="dev" + env: STABILITY="dev" before_install: - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi From 53bddb2f1a1820c813a35877e8bf8a09d6060d4b Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Sat, 29 Dec 2018 09:52:09 +0100 Subject: [PATCH 019/123] Support PSR-18 (#135) Typecheck for PSR-18 ClientInterface instead of Httplug Client interface. Our interface extends the PSR-18 ClientInterface and therefore is still accepted. No changes for the async client as there is no PSR for that (yet) --- src/BatchClient.php | 6 +++--- src/EmulatedHttpAsyncClient.php | 3 ++- src/FlexibleHttpClient.php | 9 +++++---- src/HttpClientDecorator.php | 6 +++--- src/HttpClientPool.php | 3 ++- src/HttpClientPool/HttpClientPool.php | 4 ++-- src/HttpClientPool/HttpClientPoolItem.php | 5 +++-- src/HttpClientRouter.php | 3 ++- src/HttpClientRouterInterface.php | 3 ++- src/HttpMethodsClient.php | 10 +++++----- src/Plugin/AddPathPlugin.php | 5 ++--- src/PluginClient.php | 11 ++++++----- src/PluginClientFactory.php | 8 ++++---- 13 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/BatchClient.php b/src/BatchClient.php index 09a83ff..b1cbe8d 100644 --- a/src/BatchClient.php +++ b/src/BatchClient.php @@ -3,19 +3,19 @@ namespace Http\Client\Common; use Http\Client\Exception; -use Http\Client\HttpClient; use Http\Client\Common\Exception\BatchException; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; final class BatchClient implements BatchClientInterface { /** - * @var HttpClient + * @var ClientInterface */ private $client; - public function __construct(HttpClient $client) + public function __construct(ClientInterface $client) { $this->client = $client; } diff --git a/src/EmulatedHttpAsyncClient.php b/src/EmulatedHttpAsyncClient.php index 9d6c557..d669f25 100644 --- a/src/EmulatedHttpAsyncClient.php +++ b/src/EmulatedHttpAsyncClient.php @@ -4,6 +4,7 @@ use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface; /** * Emulates an async HTTP client with the help of a synchronous client. @@ -15,7 +16,7 @@ final class EmulatedHttpAsyncClient implements HttpClient, HttpAsyncClient use HttpAsyncClientEmulator; use HttpClientDecorator; - public function __construct(HttpClient $httpClient) + public function __construct(ClientInterface $httpClient) { $this->httpClient = $httpClient; } diff --git a/src/FlexibleHttpClient.php b/src/FlexibleHttpClient.php index 58f8813..23d6e9f 100644 --- a/src/FlexibleHttpClient.php +++ b/src/FlexibleHttpClient.php @@ -4,6 +4,7 @@ use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface; /** * A flexible http client, which implements both interface and will emulate @@ -17,18 +18,18 @@ final class FlexibleHttpClient implements HttpClient, HttpAsyncClient use HttpAsyncClientDecorator; /** - * @param HttpClient|HttpAsyncClient $client + * @param ClientInterface|HttpAsyncClient $client */ public function __construct($client) { - if (!($client instanceof HttpClient) && !($client instanceof HttpAsyncClient)) { - throw new \LogicException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient'); + if (!($client instanceof ClientInterface) && !($client instanceof HttpAsyncClient)) { + throw new \LogicException('Client must be an instance of Psr\\Http\\Client\\ClientInterface or Http\\Client\\HttpAsyncClient'); } $this->httpClient = $client; $this->httpAsyncClient = $client; - if (!($this->httpClient instanceof HttpClient)) { + if (!($this->httpClient instanceof ClientInterface)) { $this->httpClient = new EmulatedHttpClient($this->httpClient); } diff --git a/src/HttpClientDecorator.php b/src/HttpClientDecorator.php index da5a1bb..9e5724c 100644 --- a/src/HttpClientDecorator.php +++ b/src/HttpClientDecorator.php @@ -2,7 +2,7 @@ namespace Http\Client\Common; -use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -14,14 +14,14 @@ trait HttpClientDecorator { /** - * @var HttpClient + * @var ClientInterface */ protected $httpClient; /** * {@inheritdoc} * - * @see HttpClient::sendRequest + * @see ClientInterface::sendRequest */ public function sendRequest(RequestInterface $request): ResponseInterface { diff --git a/src/HttpClientPool.php b/src/HttpClientPool.php index a730f50..c71f9f9 100644 --- a/src/HttpClientPool.php +++ b/src/HttpClientPool.php @@ -5,6 +5,7 @@ use Http\Client\Common\HttpClientPool\HttpClientPoolItem; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface; /** * A http client pool allows to send requests on a pool of different http client using a specific strategy (least used, @@ -15,7 +16,7 @@ interface HttpClientPool extends HttpAsyncClient, HttpClient /** * Add a client to the pool. * - * @param HttpClient|HttpAsyncClient|HttpClientPoolItem $client + * @param ClientInterface|HttpAsyncClient|HttpClientPoolItem $client */ public function addHttpClient($client); } diff --git a/src/HttpClientPool/HttpClientPool.php b/src/HttpClientPool/HttpClientPool.php index cbac805..8e2ce64 100644 --- a/src/HttpClientPool/HttpClientPool.php +++ b/src/HttpClientPool/HttpClientPool.php @@ -5,7 +5,7 @@ use Http\Client\Common\Exception\HttpClientNotFoundException; use Http\Client\Common\HttpClientPool as HttpClientPoolInterface; use Http\Client\HttpAsyncClient; -use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -23,7 +23,7 @@ abstract class HttpClientPool implements HttpClientPoolInterface /** * Add a client to the pool. * - * @param HttpClient|HttpAsyncClient|HttpClientPoolItem $client + * @param ClientInterface|HttpAsyncClient|HttpClientPoolItem $client */ public function addHttpClient($client) { diff --git a/src/HttpClientPool/HttpClientPoolItem.php b/src/HttpClientPool/HttpClientPoolItem.php index 4094502..0b382cb 100644 --- a/src/HttpClientPool/HttpClientPoolItem.php +++ b/src/HttpClientPool/HttpClientPoolItem.php @@ -5,6 +5,7 @@ use Http\Client\Common\FlexibleHttpClient; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Http\Client\Exception; use Psr\Http\Message\ResponseInterface; @@ -51,8 +52,8 @@ class HttpClientPoolItem implements HttpClient, HttpAsyncClient private $client; /** - * @param HttpClient|HttpAsyncClient $client - * @param null|int $reenableAfter Number of seconds until this client is enabled again after an error + * @param ClientInterface|HttpAsyncClient $client + * @param null|int $reenableAfter Number of seconds until this client is enabled again after an error */ public function __construct($client, $reenableAfter = null) { diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index 56be0bf..9008156 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -6,6 +6,7 @@ use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Message\RequestMatcher; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -53,7 +54,7 @@ public function addClient($client, RequestMatcher $requestMatcher) /** * Choose an HTTP client given a specific request. * - * @return HttpClient|HttpAsyncClient + * @return ClientInterface|HttpAsyncClient */ private function chooseHttpClient(RequestInterface $request) { diff --git a/src/HttpClientRouterInterface.php b/src/HttpClientRouterInterface.php index 67fb822..635e25e 100644 --- a/src/HttpClientRouterInterface.php +++ b/src/HttpClientRouterInterface.php @@ -5,6 +5,7 @@ use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Message\RequestMatcher; +use Psr\Http\Client\ClientInterface; /** * Route a request to a specific client in the stack based using a RequestMatcher. @@ -18,7 +19,7 @@ interface HttpClientRouterInterface extends HttpClient, HttpAsyncClient /** * Add a client to the router. * - * @param HttpClient|HttpAsyncClient $client + * @param ClientInterface|HttpAsyncClient $client */ public function addClient($client, RequestMatcher $requestMatcher); } diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index 69e4df9..e4ab0f6 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -2,15 +2,15 @@ namespace Http\Client\Common; -use Http\Client\HttpClient; use Http\Message\RequestFactory; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; final class HttpMethodsClient implements HttpMethodsClientInterface { /** - * @var HttpClient + * @var ClientInterface */ private $httpClient; @@ -20,10 +20,10 @@ final class HttpMethodsClient implements HttpMethodsClientInterface private $requestFactory; /** - * @param HttpClient $httpClient The client to send requests with - * @param RequestFactory $requestFactory The message factory to create requests + * @param ClientInterface $httpClient The client to send requests with + * @param RequestFactory $requestFactory The message factory to create requests */ - public function __construct(HttpClient $httpClient, RequestFactory $requestFactory) + public function __construct(ClientInterface $httpClient, RequestFactory $requestFactory) { $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; diff --git a/src/Plugin/AddPathPlugin.php b/src/Plugin/AddPathPlugin.php index 0a2adba..3b7dcaf 100644 --- a/src/Plugin/AddPathPlugin.php +++ b/src/Plugin/AddPathPlugin.php @@ -47,9 +47,8 @@ public function handleRequest(RequestInterface $request, callable $next, callabl $identifier = spl_object_hash((object) $first); if (!array_key_exists($identifier, $this->alteredRequests)) { - $request = $request->withUri($request->getUri() - ->withPath($this->uri->getPath().$request->getUri()->getPath()) - ); + $prefixedUrl = $this->uri->getPath().$request->getUri()->getPath(); + $request = $request->withUri($request->getUri()->withPath($prefixedUrl)); $this->alteredRequests[$identifier] = $identifier; } diff --git a/src/PluginClient.php b/src/PluginClient.php index 264c2fe..4c95300 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -8,6 +8,7 @@ use Http\Client\HttpClient; use Http\Client\Promise\HttpFulfilledPromise; use Http\Client\Promise\HttpRejectedPromise; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -41,9 +42,9 @@ final class PluginClient implements HttpClient, HttpAsyncClient private $options; /** - * @param HttpClient|HttpAsyncClient $client - * @param Plugin[] $plugins - * @param array $options { + * @param ClientInterface|HttpAsyncClient $client + * @param Plugin[] $plugins + * @param array $options { * * @var int $max_restarts * } @@ -54,10 +55,10 @@ public function __construct($client, array $plugins = [], array $options = []) { if ($client instanceof HttpAsyncClient) { $this->client = $client; - } elseif ($client instanceof HttpClient) { + } elseif ($client instanceof ClientInterface) { $this->client = new EmulatedHttpAsyncClient($client); } else { - throw new \RuntimeException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient'); + throw new \RuntimeException('Client must be an instance of Psr\\Http\\Client\\ClientInterface or Http\\Client\\HttpAsyncClient'); } $this->plugins = $plugins; diff --git a/src/PluginClientFactory.php b/src/PluginClientFactory.php index 77642ea..b4a78fc 100644 --- a/src/PluginClientFactory.php +++ b/src/PluginClientFactory.php @@ -3,7 +3,7 @@ namespace Http\Client\Common; use Http\Client\HttpAsyncClient; -use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface; /** * Factory to create PluginClient instances. Using this factory instead of calling PluginClient constructor will enable @@ -33,9 +33,9 @@ public static function setFactory(callable $factory) } /** - * @param HttpClient|HttpAsyncClient $client - * @param Plugin[] $plugins - * @param array $options { + * @param ClientInterface|HttpAsyncClient $client + * @param Plugin[] $plugins + * @param array $options { * * @var string $client_name to give client a name which may be used when displaying client information like in * the HTTPlugBundle profiler. From 724f0a13f17f2478e3da6a973f8123568e37189d Mon Sep 17 00:00:00 2001 From: Mponos George Date: Sat, 29 Dec 2018 11:33:56 +0200 Subject: [PATCH 020/123] Added declare strict types (#137) --- src/BatchClient.php | 2 ++ src/BatchClientInterface.php | 3 ++- src/BatchResult.php | 2 ++ src/Deferred.php | 2 ++ src/EmulatedHttpAsyncClient.php | 2 ++ src/EmulatedHttpClient.php | 2 ++ src/Exception/BatchException.php | 2 ++ src/Exception/CircularRedirectionException.php | 2 ++ src/Exception/ClientErrorException.php | 2 ++ src/Exception/HttpClientNotFoundException.php | 2 ++ src/Exception/LoopException.php | 2 ++ src/Exception/MultipleRedirectionException.php | 2 ++ src/Exception/ServerErrorException.php | 2 ++ src/FlexibleHttpClient.php | 2 ++ src/HttpAsyncClientDecorator.php | 2 ++ src/HttpAsyncClientEmulator.php | 2 ++ src/HttpClientDecorator.php | 2 ++ src/HttpClientEmulator.php | 2 ++ src/HttpClientPool.php | 2 ++ src/HttpClientPool/HttpClientPool.php | 2 ++ src/HttpClientPool/HttpClientPoolItem.php | 2 ++ src/HttpClientPool/LeastUsedClientPool.php | 2 ++ src/HttpClientPool/RandomClientPool.php | 2 ++ src/HttpClientPool/RoundRobinClientPool.php | 2 ++ src/HttpClientRouter.php | 2 ++ src/HttpClientRouterInterface.php | 2 ++ src/HttpMethodsClient.php | 2 ++ src/HttpMethodsClientInterface.php | 2 ++ src/Plugin.php | 2 ++ src/Plugin/AddHostPlugin.php | 2 ++ src/Plugin/AddPathPlugin.php | 2 ++ src/Plugin/AuthenticationPlugin.php | 2 ++ src/Plugin/BaseUriPlugin.php | 2 ++ src/Plugin/ContentLengthPlugin.php | 2 ++ src/Plugin/ContentTypePlugin.php | 2 ++ src/Plugin/CookiePlugin.php | 4 +++- src/Plugin/DecoderPlugin.php | 2 ++ src/Plugin/ErrorPlugin.php | 2 ++ src/Plugin/HeaderAppendPlugin.php | 2 ++ src/Plugin/HeaderDefaultsPlugin.php | 2 ++ src/Plugin/HeaderRemovePlugin.php | 2 ++ src/Plugin/HeaderSetPlugin.php | 2 ++ src/Plugin/HistoryPlugin.php | 2 ++ src/Plugin/Journal.php | 2 ++ src/Plugin/QueryDefaultsPlugin.php | 2 ++ src/Plugin/RedirectPlugin.php | 2 ++ src/Plugin/RequestMatcherPlugin.php | 2 ++ src/Plugin/RetryPlugin.php | 2 ++ src/Plugin/VersionBridgePlugin.php | 2 ++ src/PluginClient.php | 2 ++ src/PluginClientFactory.php | 2 ++ src/VersionBridgeClient.php | 2 ++ 52 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/BatchClient.php b/src/BatchClient.php index b1cbe8d..bd340af 100644 --- a/src/BatchClient.php +++ b/src/BatchClient.php @@ -1,5 +1,7 @@ Date: Sat, 29 Dec 2018 11:54:47 +0100 Subject: [PATCH 021/123] Added backwards compatibility layer (#133) --- src/Plugin/VersionBridgePlugin.php | 3 ++- src/VersionBridgeClient.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Plugin/VersionBridgePlugin.php b/src/Plugin/VersionBridgePlugin.php index 45c4364..0a2c714 100644 --- a/src/Plugin/VersionBridgePlugin.php +++ b/src/Plugin/VersionBridgePlugin.php @@ -4,6 +4,7 @@ namespace Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; /** @@ -16,7 +17,7 @@ trait VersionBridgePlugin { abstract protected function doHandleRequest(RequestInterface $request, callable $next, callable $first); - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { return $this->doHandleRequest($request, $next, $first); } diff --git a/src/VersionBridgeClient.php b/src/VersionBridgeClient.php index f8ab773..a608edd 100644 --- a/src/VersionBridgeClient.php +++ b/src/VersionBridgeClient.php @@ -5,6 +5,7 @@ namespace Http\Client\Common; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; /** * A client that helps you migrate from php-http/httplug 1.x to 2.x. This @@ -16,7 +17,7 @@ trait VersionBridgeClient { abstract protected function doSendRequest(RequestInterface $request); - public function sendRequest(RequestInterface $request) + public function sendRequest(RequestInterface $request): ResponseInterface { return $this->doSendRequest($request); } From 7d5fe4deb07494ddddc49296972de8adfcc59bd6 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Mon, 31 Dec 2018 12:15:51 +0100 Subject: [PATCH 022/123] init --- .travis.yml | 2 ++ phpunit.xml.dist | 14 ++++++++++++++ tests/PluginClientTest.php | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 phpunit.xml.dist create mode 100644 tests/PluginClientTest.php diff --git a/.travis.yml b/.travis.yml index 84eac28..10d366e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,8 @@ jobs: env: DEPENDENCIES="dunglas/symfony-lock:^3" - php: 7.2 env: DEPENDENCIES="dunglas/symfony-lock:^4" + - php: 7.2 + env: TEST_COMMAND="./vendor/bin/phpunit" DEPENDENCIES="phpunit/phpunit:^7.5 nyholm/psr7:^1.0" # Latest dev release - php: 7.3 diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..d353b7c --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + + + + ./tests + + + diff --git a/tests/PluginClientTest.php b/tests/PluginClientTest.php new file mode 100644 index 0000000..d88bbf1 --- /dev/null +++ b/tests/PluginClientTest.php @@ -0,0 +1,18 @@ + Date: Mon, 31 Dec 2018 12:36:17 +0100 Subject: [PATCH 023/123] Small test to prove a bug. --- tests/PluginClientTest.php | 64 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/tests/PluginClientTest.php b/tests/PluginClientTest.php index d88bbf1..47bf8c3 100644 --- a/tests/PluginClientTest.php +++ b/tests/PluginClientTest.php @@ -4,15 +4,73 @@ namespace tests\Http\Client\Common; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\HeaderAppendPlugin; +use Http\Client\Common\Plugin\RedirectPlugin; +use Http\Client\Common\PluginClient; +use Http\Client\HttpAsyncClient; +use Http\Client\Promise\HttpFulfilledPromise; +use Http\Promise\Promise; +use Nyholm\Psr7\Request; +use Nyholm\Psr7\Response; use PHPUnit\Framework\TestCase; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; class PluginClientTest extends TestCase { - private $syncClient; - private $asyncClient; + /** + * @dataProvider clientAndMethodProvider + */ + public function testRestartChain(PluginClient $client, string $method, string $returnType) + { + $request = new Request('GET', 'https://example.com'); + $result = call_user_func([$client, $method], $request); + + $this->assertInstanceOf($returnType, $result); + } - protected function setUp() + public function clientAndMethodProvider() { + $syncClient = new class implements ClientInterface { + public function sendRequest(RequestInterface $request): ResponseInterface + { + return new Response(); + } + }; + + $asyncClient = new class implements HttpAsyncClient { + public function sendAsyncRequest(RequestInterface $request) + { + return new HttpFulfilledPromise(new Response()); + } + }; + + $headerAppendPlugin = new HeaderAppendPlugin(['Content-Type'=>'text/html']); + $redirectPlugin = new RedirectPlugin(); + $restartOncePlugin = new class implements Plugin { + private $firstRun = true; + + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise + { + if ($this->firstRun) { + $this->firstRun = false; + return $first($request)->wait(); + } + return $next($request); + } + }; + + $plugins = [$headerAppendPlugin, $restartOncePlugin, $redirectPlugin]; + + $pluginClient = new PluginClient($syncClient, $plugins); + yield [$pluginClient, 'sendRequest', ResponseInterface::class]; + yield [$pluginClient, 'sendAsyncRequest', Promise::class]; + // Async + $pluginClient = new PluginClient($asyncClient, $plugins); + yield [$pluginClient, 'sendRequest', ResponseInterface::class]; + yield [$pluginClient, 'sendAsyncRequest', Promise::class]; } } \ No newline at end of file From aea593686d77bcf347d0971dbaf284b32fe6f34e Mon Sep 17 00:00:00 2001 From: Nyholm Date: Mon, 31 Dec 2018 12:43:12 +0100 Subject: [PATCH 024/123] cs --- tests/PluginClientTest.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/PluginClientTest.php b/tests/PluginClientTest.php index 47bf8c3..eb55685 100644 --- a/tests/PluginClientTest.php +++ b/tests/PluginClientTest.php @@ -33,31 +33,34 @@ public function testRestartChain(PluginClient $client, string $method, string $r public function clientAndMethodProvider() { - $syncClient = new class implements ClientInterface { + $syncClient = new class() implements ClientInterface { public function sendRequest(RequestInterface $request): ResponseInterface { return new Response(); } }; - $asyncClient = new class implements HttpAsyncClient { + $asyncClient = new class() implements HttpAsyncClient { public function sendAsyncRequest(RequestInterface $request) { return new HttpFulfilledPromise(new Response()); } }; - $headerAppendPlugin = new HeaderAppendPlugin(['Content-Type'=>'text/html']); + $headerAppendPlugin = new HeaderAppendPlugin(['Content-Type' => 'text/html']); $redirectPlugin = new RedirectPlugin(); - $restartOncePlugin = new class implements Plugin { + $restartOncePlugin = new class() implements Plugin { private $firstRun = true; public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { if ($this->firstRun) { $this->firstRun = false; - return $first($request)->wait(); + + return $first($request); } + $this->firstRun = true; + return $next($request); } }; @@ -73,4 +76,4 @@ public function handleRequest(RequestInterface $request, callable $next, callabl yield [$pluginClient, 'sendRequest', ResponseInterface::class]; yield [$pluginClient, 'sendAsyncRequest', Promise::class]; } -} \ No newline at end of file +} From 7b6cdbed4d3aa55390d2bed24e8af6385e7512a5 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 2 Jan 2019 09:30:06 +0100 Subject: [PATCH 025/123] fix cs violation introduced when merging master to 2.x --- spec/Plugin/RetryPluginSpec.php | 4 ++-- src/Plugin/RetryPlugin.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/Plugin/RetryPluginSpec.php b/spec/Plugin/RetryPluginSpec.php index 354fc4d..e01ba54 100644 --- a/spec/Plugin/RetryPluginSpec.php +++ b/spec/Plugin/RetryPluginSpec.php @@ -109,12 +109,12 @@ public function it_respects_custom_exception_decider(RequestInterface $request, $this->beConstructedWith([ 'exception_decider' => function (RequestInterface $request, Exception $e) { return false; - } + }, ]); $exception = new Exception\NetworkException('Exception', $request->getWrappedObject()); $called = false; - $next = function (RequestInterface $receivedRequest) use($exception, &$called) { + $next = function (RequestInterface $receivedRequest) use ($exception, &$called) { if ($called) { throw new \RuntimeException('Did not expect to be called multiple times'); } diff --git a/src/Plugin/RetryPlugin.php b/src/Plugin/RetryPlugin.php index d3d372a..5292ddf 100644 --- a/src/Plugin/RetryPlugin.php +++ b/src/Plugin/RetryPlugin.php @@ -48,9 +48,9 @@ final class RetryPlugin implements Plugin /** * @param array $config { * - * @var int $retries Number of retries to attempt if an exception occurs before letting the exception bubble up. - * @var callable $exception_decider A callback that gets a request and an exception to decide after a failure whether the request should be retried. - * @var callable $exception_delay A callback that gets a request, an exception and the number of retries and returns how many microseconds we should wait before trying again. + * @var int $retries Number of retries to attempt if an exception occurs before letting the exception bubble up + * @var callable $exception_decider A callback that gets a request and an exception to decide after a failure whether the request should be retried + * @var callable $exception_delay A callback that gets a request, an exception and the number of retries and returns how many microseconds we should wait before trying again * } */ public function __construct(array $config = []) From 7d80627684224fdc0089f60d39cd8d0f3868c4db Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 2 Jan 2019 09:25:16 +0100 Subject: [PATCH 026/123] add missing type declarations --- composer.json | 2 ++ src/Plugin/ContentTypePlugin.php | 17 +++++++++-------- src/Plugin/CookiePlugin.php | 12 ++++-------- src/Plugin/DecoderPlugin.php | 17 +++-------------- 4 files changed, 18 insertions(+), 30 deletions(-) diff --git a/composer.json b/composer.json index 8a6e9f4..8701a52 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,8 @@ "sebastian/comparator": ">=2" }, "suggest": { + "ext-json": "To detect JSON responses with the ContentTypePlugin", + "ext-libxml": "To detect XML responses with the ContentTypePlugin", "php-http/logger-plugin": "PSR-3 Logger plugin", "php-http/cache-plugin": "PSR-6 Cache plugin", "php-http/stopwatch-plugin": "Symfony Stopwatch plugin" diff --git a/src/Plugin/ContentTypePlugin.php b/src/Plugin/ContentTypePlugin.php index f6dcddd..f3944dd 100644 --- a/src/Plugin/ContentTypePlugin.php +++ b/src/Plugin/ContentTypePlugin.php @@ -94,11 +94,11 @@ public function handleRequest(RequestInterface $request, callable $next, callabl return $next($request); } - /** - * @param $stream StreamInterface - */ - private function isJson($stream): bool + private function isJson(StreamInterface $stream): bool { + if (!function_exists('json_decode')) { + return false; + } $stream->rewind(); json_decode($stream->getContents()); @@ -108,17 +108,18 @@ private function isJson($stream): bool /** * @param $stream StreamInterface - * - * @return \SimpleXMLElement|false */ - private function isXml($stream) + private function isXml(StreamInterface $stream): bool { + if (!function_exists('simplexml_load_string')) { + return false; + } $stream->rewind(); $previousValue = libxml_use_internal_errors(true); $isXml = simplexml_load_string($stream->getContents()); libxml_use_internal_errors($previousValue); - return $isXml; + return false !== $isXml; } } diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index 8f8d5b0..8399210 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -91,15 +91,13 @@ public function handleRequest(RequestInterface $request, callable $next, callabl /** * Creates a cookie from a string. * - * @param $setCookie - * * @return Cookie|null * * @throws TransferException */ - private function createCookie(RequestInterface $request, $setCookie) + private function createCookie(RequestInterface $request, string $setCookieHeader) { - $parts = array_map('trim', explode(';', $setCookie)); + $parts = array_map('trim', explode(';', $setCookieHeader)); if (empty($parts) || !strpos($parts[0], '=')) { return null; @@ -169,11 +167,9 @@ private function createCookie(RequestInterface $request, $setCookie) /** * Separates key/value pair from cookie. * - * @param $part - * - * @return array + * @param string $part A single cookie value in format key=value */ - private function createValueKey($part) + private function createValueKey(string $part): array { $parts = explode('=', $part, 2); $key = trim($parts[0]); diff --git a/src/Plugin/DecoderPlugin.php b/src/Plugin/DecoderPlugin.php index b1ede5d..271ad3c 100644 --- a/src/Plugin/DecoderPlugin.php +++ b/src/Plugin/DecoderPlugin.php @@ -68,12 +68,8 @@ public function handleRequest(RequestInterface $request, callable $next, callabl /** * Decode a response body given its Transfer-Encoding or Content-Encoding value. - * - * @param ResponseInterface $response Response to decode - * - * @return ResponseInterface New response decoded */ - private function decodeResponse(ResponseInterface $response) + private function decodeResponse(ResponseInterface $response): ResponseInterface { $response = $this->decodeOnEncodingHeader('Transfer-Encoding', $response); @@ -86,13 +82,8 @@ private function decodeResponse(ResponseInterface $response) /** * Decode a response on a specific header (content encoding or transfer encoding mainly). - * - * @param string $headerName Name of the header - * @param ResponseInterface $response Response - * - * @return ResponseInterface A new instance of the response decoded */ - private function decodeOnEncodingHeader($headerName, ResponseInterface $response) + private function decodeOnEncodingHeader(string $headerName, ResponseInterface $response): ResponseInterface { if ($response->hasHeader($headerName)) { $encodings = $response->getHeader($headerName); @@ -123,11 +114,9 @@ private function decodeOnEncodingHeader($headerName, ResponseInterface $response /** * Decorate a stream given an encoding. * - * @param string $encoding - * * @return StreamInterface|false A new stream interface or false if encoding is not supported */ - private function decorateStream($encoding, StreamInterface $stream) + private function decorateStream(string $encoding, StreamInterface $stream) { if ('chunked' === strtolower($encoding)) { return new Encoding\DechunkStream($stream); From fc04c8e3e16801e2dc04ecf68f50fd7d76a3bd5e Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Thu, 3 Jan 2019 13:31:32 +0100 Subject: [PATCH 027/123] Dont add path to an url we already added a path to. (#141) --- CHANGELOG.md | 3 +- spec/Plugin/AddPathPluginSpec.php | 4 +- src/Plugin/AddPathPlugin.php | 43 ++++++++++----- tests/Plugin/AddPathPluginTest.php | 85 ++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 tests/Plugin/AddPathPluginTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d028df3..f636a0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Added interfaces for BatchClient, HttpClientRouter and HttpMethodsClient. (These interfaces use the `Interface` suffix to avoid name collisions.) - Added an interface for HttpClientPool and moved the abstract class to the HttpClientPool sub namespace. +- AddPathPlugin: Do not add the prefix if the URL already has the same prefix. ### Removed - Deprecated option `debug_plugins` has been removed from `PluginClient` @@ -18,7 +19,7 @@ ### Changed -- [RetryPlugin] Renamed the configuration options for the exception retry callback from `decider` to `exception_decider` +- RetryPlugin: Renamed the configuration options for the exception retry callback from `decider` to `exception_decider` and `delay` to `exception_delay`. The old names still work but are deprecated. ## 1.8.2 - 2018-12-14 diff --git a/spec/Plugin/AddPathPluginSpec.php b/spec/Plugin/AddPathPluginSpec.php index 6491069..1c6f09d 100644 --- a/spec/Plugin/AddPathPluginSpec.php +++ b/spec/Plugin/AddPathPluginSpec.php @@ -37,9 +37,9 @@ public function it_adds_path( $host->getPath()->shouldBeCalled()->willReturn('/api'); $request->getUri()->shouldBeCalled()->willReturn($uri); - $request->withUri($uri)->shouldBeCalled()->willReturn($request); + $request->withUri($uri)->shouldBeCalledTimes(1)->willReturn($request); - $uri->withPath('/api/users')->shouldBeCalled()->willReturn($uri); + $uri->withPath('/api/users')->shouldBeCalledTimes(1)->willReturn($uri); $uri->getPath()->shouldBeCalled()->willReturn('/users'); $this->beConstructedWith($host); diff --git a/src/Plugin/AddPathPlugin.php b/src/Plugin/AddPathPlugin.php index 8b92514..9d43104 100644 --- a/src/Plugin/AddPathPlugin.php +++ b/src/Plugin/AddPathPlugin.php @@ -21,13 +21,6 @@ final class AddPathPlugin implements Plugin */ private $uri; - /** - * Stores identifiers of the already altered requests. - * - * @var array - */ - private $alteredRequests = []; - public function __construct(UriInterface $uri) { if ('' === $uri->getPath()) { @@ -42,16 +35,42 @@ public function __construct(UriInterface $uri) } /** + * Adds a prefix in the beginning of the URL's path. + * + * The prefix is not added if that prefix is already on the URL's path. This will fail on the edge + * case of the prefix being repeated, for example if `https://example.com/api/api/foo` is a valid + * URL on the server and the configured prefix is `/api`. + * + * We looked at other solutions, but they are all much more complicated, while still having edge + * cases: + * - Doing an spl_object_hash on `$first` will lead to collisions over time because over time the + * hash can collide. + * - Have the PluginClient provide a magic header to identify the request chain and only apply + * this plugin once. + * + * There are 2 reasons for the AddPathPlugin to be executed twice on the same request: + * - A plugin can restart the chain by calling `$first`, e.g. redirect + * - A plugin can call `$next` more than once, e.g. retry + * + * Depending on the scenario, the path should or should not be added. E.g. `$first` could + * be called after a redirect response from the server. The server likely already has the + * correct path. + * + * No solution fits all use cases. This implementation will work fine for the common use cases. + * If you have a specific situation where this is not the right thing, you can build a custom plugin + * that does exactly what you need. + * * {@inheritdoc} */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { - $identifier = spl_object_hash((object) $first); + $prepend = $this->uri->getPath(); + $path = $request->getUri()->getPath(); - if (!array_key_exists($identifier, $this->alteredRequests)) { - $prefixedUrl = $this->uri->getPath().$request->getUri()->getPath(); - $request = $request->withUri($request->getUri()->withPath($prefixedUrl)); - $this->alteredRequests[$identifier] = $identifier; + if (substr($path, 0, strlen($prepend)) !== $prepend) { + $request = $request->withUri($request->getUri() + ->withPath($prepend.$path) + ); } return $next($request); diff --git a/tests/Plugin/AddPathPluginTest.php b/tests/Plugin/AddPathPluginTest.php new file mode 100644 index 0000000..3980fa4 --- /dev/null +++ b/tests/Plugin/AddPathPluginTest.php @@ -0,0 +1,85 @@ +first = function () {}; + $this->plugin = new AddPathPlugin(new Uri('/api')); + } + + public function testRewriteSameUrl() + { + $verify = function (RequestInterface $request) { + $this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString()); + }; + + $request = new Request('GET', 'https://example.com/foo', ['Content-Type'=>'text/html']); + $this->plugin->handleRequest($request, $verify, $this->first); + + // Make a second call with the same $request object + $this->plugin->handleRequest($request, $verify, $this->first); + + // Make a new call with a new object but same URL + $request = new Request('GET', 'https://example.com/foo', ['Content-Type'=>'text/plain']); + $this->plugin->handleRequest($request, $verify, $this->first); + } + + public function testRewriteCallingThePluginTwice() + { + $request = new Request('GET', 'https://example.com/foo'); + $this->plugin->handleRequest($request, function (RequestInterface $request) { + $this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString()); + + // Run the plugin again with the modified request + $this->plugin->handleRequest($request, function (RequestInterface $request) { + $this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString()); + }, $this->first); + }, $this->first); + } + + public function testRewriteWithDifferentUrl() + { + $request = new Request('GET', 'https://example.com/foo'); + $this->plugin->handleRequest($request, function (RequestInterface $request) { + $this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString()); + }, $this->first); + + $request = new Request('GET', 'https://example.com/bar'); + $this->plugin->handleRequest($request, function (RequestInterface $request) { + $this->assertEquals('https://example.com/api/bar', $request->getUri()->__toString()); + }, $this->first); + } + + public function testRewriteWhenPathIsIncluded() + { + $verify = function (RequestInterface $request) { + $this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString()); + }; + + $request = new Request('GET', 'https://example.com/api/foo'); + $this->plugin->handleRequest($request, $verify, $this->first); + } +} From 414cde18d63b6e9587cdb1e01c1efba371f414a5 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 3 Jan 2019 13:54:48 +0100 Subject: [PATCH 028/123] update composer.json branch alias --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8701a52..821fe16 100644 --- a/composer.json +++ b/composer.json @@ -47,8 +47,7 @@ }, "extra": { "branch-alias": { - "dev-2.x": "2.x-dev", - "dev-master": "1.9.x-dev" + "dev-master": "2.x-dev" } } } From 10b6df5b04295f980337f391e7ed905ed593cde4 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Thu, 3 Jan 2019 18:06:02 +0000 Subject: [PATCH 029/123] A better branch alias would be 2.0.x --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 821fe16..53f91e1 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "2.0.x-dev" } } } From 0d3573e5ee370ffce7c8254de49b98c16c49c705 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 4 Jan 2019 08:29:12 +0100 Subject: [PATCH 030/123] Drop PHP7.0 --- .travis.yml | 3 +-- composer.json | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 10d366e..cf3a664 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ cache: - $HOME/.composer/cache/files php: -- 7.0 - 7.1 - 7.2 - 7.3 @@ -16,7 +15,7 @@ matrix: jobs: include: - - php: 7.0 + - php: 7.1 env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" - php: 7.2 env: COVERAGE=true DEPENDENCIES="leanphp/phpspec-code-coverage" diff --git a/composer.json b/composer.json index 53f91e1..fa5aa37 100644 --- a/composer.json +++ b/composer.json @@ -11,18 +11,18 @@ } ], "require": { - "php": "^7.0", + "php": "^7.1", "php-http/httplug": "^2.0", "php-http/message-factory": "^1.0", "php-http/message": "^1.6", - "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0" + "symfony/options-resolver": " ^3.4.20 || ^4.0.15 || ^4.1.9 || ^4.2.1" }, "require-dev": { - "doctrine/instantiator": ">=1.0.5", + "doctrine/instantiator": "^1.1", "guzzlehttp/psr7": "^1.4", "phpspec/phpspec": "^3.4 || ^4.2", - "phpspec/prophecy": ">=1.8", - "sebastian/comparator": ">=2" + "phpspec/prophecy": "^1.8", + "sebastian/comparator": "^2.1.3 || ^3.0" }, "suggest": { "ext-json": "To detect JSON responses with the ContentTypePlugin", From 3f2b7a7472f930e95b4207db0cae97e98adfe6b2 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 4 Jan 2019 08:37:19 +0100 Subject: [PATCH 031/123] Drop more versions of dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index fa5aa37..9d65c4a 100644 --- a/composer.json +++ b/composer.json @@ -20,9 +20,9 @@ "require-dev": { "doctrine/instantiator": "^1.1", "guzzlehttp/psr7": "^1.4", - "phpspec/phpspec": "^3.4 || ^4.2", + "phpspec/phpspec": "^5.1", "phpspec/prophecy": "^1.8", - "sebastian/comparator": "^2.1.3 || ^3.0" + "sebastian/comparator": "^3.0" }, "suggest": { "ext-json": "To detect JSON responses with the ContentTypePlugin", From 4a55debd642370b23ed7484d6b7a191256a43f5e Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 4 Jan 2019 08:46:06 +0100 Subject: [PATCH 032/123] Fixes --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index cf3a664..9e10589 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,15 +18,13 @@ jobs: - php: 7.1 env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" - php: 7.2 - env: COVERAGE=true DEPENDENCIES="leanphp/phpspec-code-coverage" + env: COVERAGE=true DEPENDENCIES="leanphp/phpspec-code-coverage phpspec/phpspec:^4.2" script: - composer test-ci after_success: - wget https://scrutinizer-ci.com/ocular.phar - php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml --revision=$TRAVIS_COMMIT # Test LTS versions - - php: 7.2 - env: DEPENDENCIES="dunglas/symfony-lock:^2" - php: 7.2 env: DEPENDENCIES="dunglas/symfony-lock:^3" - php: 7.2 From 5c220bdfd75933ab8b40fc03833b34c591076b95 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 4 Jan 2019 08:53:57 +0100 Subject: [PATCH 033/123] Add else option to the RequestMatcherPlugin --- src/Plugin/RequestMatcherPlugin.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Plugin/RequestMatcherPlugin.php b/src/Plugin/RequestMatcherPlugin.php index fdf9377..3a3ff8f 100644 --- a/src/Plugin/RequestMatcherPlugin.php +++ b/src/Plugin/RequestMatcherPlugin.php @@ -22,14 +22,20 @@ final class RequestMatcherPlugin implements Plugin private $requestMatcher; /** - * @var Plugin + * @var null|Plugin */ - private $delegatedPlugin; + private $successPlugin; - public function __construct(RequestMatcher $requestMatcher, Plugin $delegatedPlugin) + /** + * @var null|Plugin + */ + private $failurePlugin; + + public function __construct(RequestMatcher $requestMatcher, ?Plugin $delegateOnMatch, Plugin $delegateOnNoMatch = null) { $this->requestMatcher = $requestMatcher; - $this->delegatedPlugin = $delegatedPlugin; + $this->successPlugin = $delegateOnMatch; + $this->failurePlugin = $delegateOnNoMatch; } /** @@ -38,7 +44,11 @@ public function __construct(RequestMatcher $requestMatcher, Plugin $delegatedPlu public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { if ($this->requestMatcher->matches($request)) { - return $this->delegatedPlugin->handleRequest($request, $next, $first); + if (null !== $this->successPlugin) { + return $this->successPlugin->handleRequest($request, $next, $first); + } + } elseif (null !== $this->failurePlugin) { + return $this->failurePlugin->handleRequest($request, $next, $first); } return $next($request); From b139c508370feaf912bbf09ecc2c28b6d064e04d Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 4 Jan 2019 10:05:47 +0100 Subject: [PATCH 034/123] Made exceptions final --- CHANGELOG.md | 1 + src/Exception/CircularRedirectionException.php | 2 +- src/Exception/ClientErrorException.php | 2 +- src/Exception/HttpClientNotFoundException.php | 2 +- src/Exception/LoopException.php | 2 +- src/Exception/MultipleRedirectionException.php | 2 +- src/Exception/ServerErrorException.php | 2 +- 7 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e5e0a0..057ec25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ (These interfaces use the `Interface` suffix to avoid name collisions.) - Added an interface for HttpClientPool and moved the abstract class to the HttpClientPool sub namespace. - AddPathPlugin: Do not add the prefix if the URL already has the same prefix. +- All exceptions in `Http\Client\Common\Exception` are final. ### Removed - Deprecated option `debug_plugins` has been removed from `PluginClient` diff --git a/src/Exception/CircularRedirectionException.php b/src/Exception/CircularRedirectionException.php index de2063e..9db927c 100644 --- a/src/Exception/CircularRedirectionException.php +++ b/src/Exception/CircularRedirectionException.php @@ -11,6 +11,6 @@ * * @author Joel Wurtz */ -class CircularRedirectionException extends HttpException +final class CircularRedirectionException extends HttpException { } diff --git a/src/Exception/ClientErrorException.php b/src/Exception/ClientErrorException.php index 1b187e7..c657a3f 100644 --- a/src/Exception/ClientErrorException.php +++ b/src/Exception/ClientErrorException.php @@ -11,6 +11,6 @@ * * @author Joel Wurtz */ -class ClientErrorException extends HttpException +final class ClientErrorException extends HttpException { } diff --git a/src/Exception/HttpClientNotFoundException.php b/src/Exception/HttpClientNotFoundException.php index 5f16053..509daa5 100644 --- a/src/Exception/HttpClientNotFoundException.php +++ b/src/Exception/HttpClientNotFoundException.php @@ -11,6 +11,6 @@ * * @author Joel Wurtz */ -class HttpClientNotFoundException extends TransferException +final class HttpClientNotFoundException extends TransferException { } diff --git a/src/Exception/LoopException.php b/src/Exception/LoopException.php index e84053c..f4e173f 100644 --- a/src/Exception/LoopException.php +++ b/src/Exception/LoopException.php @@ -11,6 +11,6 @@ * * @author Joel Wurtz */ -class LoopException extends RequestException +final class LoopException extends RequestException { } diff --git a/src/Exception/MultipleRedirectionException.php b/src/Exception/MultipleRedirectionException.php index 4c7814e..bf6c9f7 100644 --- a/src/Exception/MultipleRedirectionException.php +++ b/src/Exception/MultipleRedirectionException.php @@ -11,6 +11,6 @@ * * @author Joel Wurtz */ -class MultipleRedirectionException extends HttpException +final class MultipleRedirectionException extends HttpException { } diff --git a/src/Exception/ServerErrorException.php b/src/Exception/ServerErrorException.php index 1ad2552..774b97f 100644 --- a/src/Exception/ServerErrorException.php +++ b/src/Exception/ServerErrorException.php @@ -11,6 +11,6 @@ * * @author Joel Wurtz */ -class ServerErrorException extends HttpException +final class ServerErrorException extends HttpException { } From a2daefa15334bb9956feeba105e94f5c1b5b7f34 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 4 Jan 2019 10:08:02 +0100 Subject: [PATCH 035/123] Only show build status for master (#154) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 017bfce..7d13658 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Latest Version](https://img.shields.io/github/release/php-http/client-common.svg?style=flat-square)](https://github.com/php-http/client-common/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/client-common.svg?style=flat-square)](https://travis-ci.org/php-http/client-common) +[![Build Status](https://img.shields.io/travis/php-http/client-common/master.svg?style=flat-square)](https://travis-ci.org/php-http/client-common) [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/client-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/client-common) [![Quality Score](https://img.shields.io/scrutinizer/g/php-http/client-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/client-common) [![Total Downloads](https://img.shields.io/packagist/dt/php-http/client-common.svg?style=flat-square)](https://packagist.org/packages/php-http/client-common) From de6dfdf9999acdad5d1fda2bff5277dae103def1 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 4 Jan 2019 10:30:34 +0100 Subject: [PATCH 036/123] Added HttpClientNoMatchException for HttpClientRouter (#153) --- CHANGELOG.md | 1 + spec/HttpClientRouterSpec.php | 6 ++-- src/Exception/HttpClientNoMatchException.php | 30 ++++++++++++++++++++ src/HttpClientRouter.php | 4 +-- 4 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 src/Exception/HttpClientNoMatchException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 057ec25..7f3d0f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 2.0 (unreleased) ### Changed +- HttpClientRouter now throws a HttpClientNoMatchException instead of a RequestException if it can not find a client for the request. - RetryPlugin will no longer retry requests when the response failed with a HTTP code < 500. - Abstract method `HttpClientPool::chooseHttpClient()` has now an explicit return type (`Http\Client\Common\HttpClientPoolItem`) - Interface method `Plugin::handleRequest(...)` has now an explicit return type (`Http\Promise\Promise`) diff --git a/spec/HttpClientRouterSpec.php b/spec/HttpClientRouterSpec.php index a409203..f90c034 100644 --- a/spec/HttpClientRouterSpec.php +++ b/spec/HttpClientRouterSpec.php @@ -2,6 +2,7 @@ namespace spec\Http\Client\Common; +use Http\Client\Common\Exception\HttpClientNoMatchException; use Http\Client\Common\HttpClientRouter; use Http\Message\RequestMatcher; use Http\Client\HttpAsyncClient; @@ -11,7 +12,6 @@ use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; use Http\Client\Common\HttpClientRouterInterface; -use Http\Client\Exception\RequestException; class HttpClientRouterSpec extends ObjectBehavior { @@ -58,7 +58,7 @@ public function it_throw_exception_on_send_request(RequestMatcher $matcher, Http $this->addClient($client, $matcher); $matcher->matches($request)->willReturn(false); - $this->shouldThrow(RequestException::class)->duringSendRequest($request); + $this->shouldThrow(HttpClientNoMatchException::class)->duringSendRequest($request); } public function it_throw_exception_on_send_async_request(RequestMatcher $matcher, HttpAsyncClient $client, RequestInterface $request) @@ -66,6 +66,6 @@ public function it_throw_exception_on_send_async_request(RequestMatcher $matcher $this->addClient($client, $matcher); $matcher->matches($request)->willReturn(false); - $this->shouldThrow(RequestException::class)->duringSendAsyncRequest($request); + $this->shouldThrow(HttpClientNoMatchException::class)->duringSendAsyncRequest($request); } } diff --git a/src/Exception/HttpClientNoMatchException.php b/src/Exception/HttpClientNoMatchException.php new file mode 100644 index 0000000..437467c --- /dev/null +++ b/src/Exception/HttpClientNoMatchException.php @@ -0,0 +1,30 @@ + + */ +final class HttpClientNoMatchException extends TransferException +{ + private $request; + + public function __construct(string $message, RequestInterface $request, \Exception $previous = null) + { + $this->request = $request; + + parent::__construct($message, 0, $previous); + } + + public function getRequest(): RequestInterface + { + return $this->request; + } +} diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index 37f8c3c..ccd4a1c 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -4,7 +4,7 @@ namespace Http\Client\Common; -use Http\Client\Exception\RequestException; +use Http\Client\Common\Exception\HttpClientNoMatchException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Message\RequestMatcher; @@ -66,6 +66,6 @@ private function chooseHttpClient(RequestInterface $request) } } - throw new RequestException('No client found for the specified request', $request); + throw new HttpClientNoMatchException('No client found for the specified request', $request); } } From 281018174f9f5866154e133a7ce7035d08512976 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sat, 29 Dec 2018 10:33:42 +0100 Subject: [PATCH 037/123] no exception should be thrown on server error, but retrying those would make sense --- CHANGELOG.md | 5 +- spec/Plugin/RetryPluginSpec.php | 18 ++++-- src/Plugin/RetryPlugin.php | 97 ++++++++++++++++++++++----------- 3 files changed, 83 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f3d0f7..a45fcba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ ### Changed - HttpClientRouter now throws a HttpClientNoMatchException instead of a RequestException if it can not find a client for the request. -- RetryPlugin will no longer retry requests when the response failed with a HTTP code < 500. +- RetryPlugin will only retry exceptions when there is no response, or a response in the 5xx HTTP code range. +- RetryPlugin also retries when no exception is thrown if the responses has HTTP code in the 5xx range. + The callbacks for exception handling have been renamed and callbacks for response handling have been added. - Abstract method `HttpClientPool::chooseHttpClient()` has now an explicit return type (`Http\Client\Common\HttpClientPoolItem`) - Interface method `Plugin::handleRequest(...)` has now an explicit return type (`Http\Promise\Promise`) - Made classes final that are not intended to be extended. @@ -16,6 +18,7 @@ ### Removed - Deprecated option `debug_plugins` has been removed from `PluginClient` +- Deprecated options `decider` and `delay` have been removed from `RetryPlugin`, use `exception_decider` and `exception_delay` instead. ## 1.9.0 - 2019-01-03 diff --git a/spec/Plugin/RetryPluginSpec.php b/spec/Plugin/RetryPluginSpec.php index e01ba54..41bf059 100644 --- a/spec/Plugin/RetryPluginSpec.php +++ b/spec/Plugin/RetryPluginSpec.php @@ -150,11 +150,19 @@ public function it_does_not_keep_history_of_old_failure(RequestInterface $reques $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); } - public function it_has_an_exponential_default_delay(RequestInterface $request, Exception\HttpException $exception) + public function it_has_an_exponential_default_error_response_delay(RequestInterface $request, ResponseInterface $response) { - $this->defaultDelay($request, $exception, 0)->shouldBe(500000); - $this->defaultDelay($request, $exception, 1)->shouldBe(1000000); - $this->defaultDelay($request, $exception, 2)->shouldBe(2000000); - $this->defaultDelay($request, $exception, 3)->shouldBe(4000000); + $this->defaultErrorResponseDelay($request, $response, 0)->shouldBe(500000); + $this->defaultErrorResponseDelay($request, $response, 1)->shouldBe(1000000); + $this->defaultErrorResponseDelay($request, $response, 2)->shouldBe(2000000); + $this->defaultErrorResponseDelay($request, $response, 3)->shouldBe(4000000); + } + + public function it_has_an_exponential_default_exception_delay(RequestInterface $request, Exception\HttpException $exception) + { + $this->defaultExceptionDelay($request, $exception, 0)->shouldBe(500000); + $this->defaultExceptionDelay($request, $exception, 1)->shouldBe(1000000); + $this->defaultExceptionDelay($request, $exception, 2)->shouldBe(2000000); + $this->defaultExceptionDelay($request, $exception, 3)->shouldBe(4000000); } } diff --git a/src/Plugin/RetryPlugin.php b/src/Plugin/RetryPlugin.php index 5292ddf..d13e71e 100644 --- a/src/Plugin/RetryPlugin.php +++ b/src/Plugin/RetryPlugin.php @@ -31,13 +31,23 @@ final class RetryPlugin implements Plugin /** * @var callable */ - private $exceptionDelay; + private $errorResponseDelay; + + /** + * @var callable + */ + private $errorResponseDecider; /** * @var callable */ private $exceptionDecider; + /** + * @var callable + */ + private $exceptionDelay; + /** * Store the retry counter for each request. * @@ -49,44 +59,39 @@ final class RetryPlugin implements Plugin * @param array $config { * * @var int $retries Number of retries to attempt if an exception occurs before letting the exception bubble up + * @var callable $error_response_decider A callback that gets a request and response to decide whether the request should be retried * @var callable $exception_decider A callback that gets a request and an exception to decide after a failure whether the request should be retried - * @var callable $exception_delay A callback that gets a request, an exception and the number of retries and returns how many microseconds we should wait before trying again + * @var callable $error_response_delay A callback that gets a request and response and the current number of retries and returns how many microseconds we should wait before trying again + * @var callable $exception_delay A callback that gets a request, an exception and the current number of retries and returns how many microseconds we should wait before trying again * } */ public function __construct(array $config = []) { - if (array_key_exists('decider', $config)) { - if (array_key_exists('exception_decider', $config)) { - throw new \InvalidArgumentException('Do not set both the old "decider" and new "exception_decider" options'); - } - trigger_error('The "decider" option has been deprecated in favour of "exception_decider"', E_USER_DEPRECATED); - $config['exception_decider'] = $config['decider']; - unset($config['decider']); - } - if (array_key_exists('delay', $config)) { - if (array_key_exists('exception_delay', $config)) { - throw new \InvalidArgumentException('Do not set both the old "delay" and new "exception_delay" options'); - } - trigger_error('The "delay" option has been deprecated in favour of "exception_delay"', E_USER_DEPRECATED); - $config['exception_delay'] = $config['delay']; - unset($config['delay']); - } - $resolver = new OptionsResolver(); $resolver->setDefaults([ 'retries' => 1, + 'error_response_decider' => function (RequestInterface $request, ResponseInterface $response) { + // do not retry client errors + return $response->getStatusCode() >= 500 && $response->getStatusCode() < 600; + }, 'exception_decider' => function (RequestInterface $request, Exception $e) { // do not retry client errors - return !$e instanceof HttpException || $e->getCode() >= 500; + return !$e instanceof HttpException || $e->getCode() >= 500 && $e->getCode() < 600; }, - 'exception_delay' => __CLASS__.'::defaultDelay', + 'error_response_delay' => __CLASS__.'::defaultErrorResponseDelay', + 'exception_delay' => __CLASS__.'::defaultExceptionDelay', ]); + $resolver->setAllowedTypes('retries', 'int'); + $resolver->setAllowedTypes('error_response_decider', 'callable'); $resolver->setAllowedTypes('exception_decider', 'callable'); + $resolver->setAllowedTypes('error_response_delay', 'callable'); $resolver->setAllowedTypes('exception_delay', 'callable'); $options = $resolver->resolve($config); $this->retry = $options['retries']; + $this->errorResponseDecider = $options['error_response_decider']; + $this->errorResponseDelay = $options['error_response_delay']; $this->exceptionDecider = $options['exception_decider']; $this->exceptionDelay = $options['exception_delay']; } @@ -98,7 +103,22 @@ public function handleRequest(RequestInterface $request, callable $next, callabl { $chainIdentifier = spl_object_hash((object) $first); - return $next($request)->then(function (ResponseInterface $response) use ($request, $chainIdentifier) { + return $next($request)->then(function (ResponseInterface $response) use ($request, $next, $first, $chainIdentifier) { + if (!array_key_exists($chainIdentifier, $this->retryStorage)) { + $this->retryStorage[$chainIdentifier] = 0; + } + + if ($this->retryStorage[$chainIdentifier] >= $this->retry) { + unset($this->retryStorage[$chainIdentifier]); + + return $response; + } + + if (call_user_func($this->errorResponseDecider, $request, $response)) { + $time = call_user_func($this->errorResponseDelay, $request, $response, $this->retryStorage[$chainIdentifier]); + $response = $this->retry($request, $next, $first, $chainIdentifier, $time); + } + if (array_key_exists($chainIdentifier, $this->retryStorage)) { unset($this->retryStorage[$chainIdentifier]); } @@ -120,23 +140,38 @@ public function handleRequest(RequestInterface $request, callable $next, callabl } $time = call_user_func($this->exceptionDelay, $request, $exception, $this->retryStorage[$chainIdentifier]); - usleep($time); - - // Retry synchronously - ++$this->retryStorage[$chainIdentifier]; - $promise = $this->handleRequest($request, $next, $first); - return $promise->wait(); + return $this->retry($request, $next, $first, $chainIdentifier, $time); }); } /** * @param int $retries The number of retries we made before. First time this get called it will be 0. - * - * @return int */ - public static function defaultDelay(RequestInterface $request, Exception $e, $retries) + public static function defaultErrorResponseDelay(RequestInterface $request, ResponseInterface $response, int $retries): int { return pow(2, $retries) * 500000; } + + /** + * @param int $retries The number of retries we made before. First time this get called it will be 0. + */ + public static function defaultExceptionDelay(RequestInterface $request, Exception $e, int $retries): int + { + return pow(2, $retries) * 500000; + } + + /** + * @throws \Exception if retrying returns a failed promise + */ + private function retry(RequestInterface $request, callable $next, callable $first, string $chainIdentifier, int $delay): ResponseInterface + { + usleep($delay); + + // Retry synchronously + ++$this->retryStorage[$chainIdentifier]; + $promise = $this->handleRequest($request, $next, $first); + + return $promise->wait(); + } } From bd9812fce5387742d51709c6d9ac41d45cee68da Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sat, 5 Jan 2019 07:59:19 +0100 Subject: [PATCH 038/123] type declarations for php 7.1 (#156) --- spec/Plugin/ErrorPluginSpec.php | 8 ++-- spec/Plugin/RedirectPluginSpec.php | 18 ++++----- src/Deferred.php | 4 +- src/Exception/BatchException.php | 4 +- src/HttpClientPool.php | 2 +- src/HttpClientPool/HttpClientPool.php | 2 +- src/HttpClientPool/HttpClientPoolItem.php | 8 ++-- src/HttpClientRouter.php | 2 +- src/HttpClientRouterInterface.php | 2 +- src/HttpMethodsClient.php | 2 +- src/HttpMethodsClientInterface.php | 2 +- src/Plugin/AddHostPlugin.php | 2 +- src/Plugin/CookiePlugin.php | 6 +-- src/Plugin/RedirectPlugin.php | 45 ++++++++--------------- 14 files changed, 46 insertions(+), 61 deletions(-) diff --git a/spec/Plugin/ErrorPluginSpec.php b/spec/Plugin/ErrorPluginSpec.php index 67e5c7e..205192a 100644 --- a/spec/Plugin/ErrorPluginSpec.php +++ b/spec/Plugin/ErrorPluginSpec.php @@ -27,7 +27,7 @@ public function it_is_a_plugin() public function it_throw_client_error_exception_on_4xx_error(RequestInterface $request, ResponseInterface $response) { - $response->getStatusCode()->willReturn('400'); + $response->getStatusCode()->willReturn(400); $response->getReasonPhrase()->willReturn('Bad request'); $next = function (RequestInterface $receivedRequest) use ($request, $response) { @@ -45,7 +45,7 @@ public function it_does_not_throw_client_error_exception_on_4xx_error_if_only_se { $this->beConstructedWith(['only_server_exception' => true]); - $response->getStatusCode()->willReturn('400'); + $response->getStatusCode()->willReturn(400); $response->getReasonPhrase()->willReturn('Bad request'); $next = function (RequestInterface $receivedRequest) use ($request, $response) { @@ -59,7 +59,7 @@ public function it_does_not_throw_client_error_exception_on_4xx_error_if_only_se public function it_throw_server_error_exception_on_5xx_error(RequestInterface $request, ResponseInterface $response) { - $response->getStatusCode()->willReturn('500'); + $response->getStatusCode()->willReturn(500); $response->getReasonPhrase()->willReturn('Server error'); $next = function (RequestInterface $receivedRequest) use ($request, $response) { @@ -75,7 +75,7 @@ public function it_throw_server_error_exception_on_5xx_error(RequestInterface $r public function it_returns_response(RequestInterface $request, ResponseInterface $response) { - $response->getStatusCode()->willReturn('200'); + $response->getStatusCode()->willReturn(200); $next = function (RequestInterface $receivedRequest) use ($request, $response) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { diff --git a/spec/Plugin/RedirectPluginSpec.php b/spec/Plugin/RedirectPluginSpec.php index de9b30a..3342f60 100644 --- a/spec/Plugin/RedirectPluginSpec.php +++ b/spec/Plugin/RedirectPluginSpec.php @@ -37,7 +37,7 @@ public function it_redirects_on_302( ResponseInterface $finalResponse, Promise $promise ) { - $responseRedirect->getStatusCode()->willReturn('302'); + $responseRedirect->getStatusCode()->willReturn(302); $responseRedirect->hasHeader('Location')->willReturn(true); $responseRedirect->getHeaderLine('Location')->willReturn('/redirect'); @@ -156,7 +156,7 @@ public function it_replace_full_url( $request->getUri()->willReturn($uri); $uri->__toString()->willReturn('/original'); - $responseRedirect->getStatusCode()->willReturn('302'); + $responseRedirect->getStatusCode()->willReturn(302); $responseRedirect->hasHeader('Location')->willReturn(true); $responseRedirect->getHeaderLine('Location')->willReturn('https://server.com:8000/redirect?query#fragment'); @@ -203,7 +203,7 @@ public function it_throws_http_exception_on_no_location(RequestInterface $reques $request->getUri()->willReturn($uri); $uri->__toString()->willReturn('/original'); - $responseRedirect->getStatusCode()->willReturn('302'); + $responseRedirect->getStatusCode()->willReturn(302); $responseRedirect->hasHeader('Location')->willReturn(false); $promise = $this->handleRequest($request, $next, function () {}); @@ -223,7 +223,7 @@ public function it_throws_http_exception_on_invalid_location(RequestInterface $r $uri->__toString()->willReturn('/original'); $responseRedirect->getHeaderLine('Location')->willReturn('scheme:///invalid'); - $responseRedirect->getStatusCode()->willReturn('302'); + $responseRedirect->getStatusCode()->willReturn(302); $responseRedirect->hasHeader('Location')->willReturn(true); $promise = $this->handleRequest($request, $next, function () {}); @@ -240,7 +240,7 @@ public function it_throw_multi_redirect_exception_on_300(RequestInterface $reque }; $this->beConstructedWith(['preserve_header' => true, 'use_default_for_multiple' => false]); - $responseRedirect->getStatusCode()->willReturn('300'); + $responseRedirect->getStatusCode()->willReturn(300); $promise = $this->handleRequest($request, $next, function () {}); $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); @@ -255,7 +255,7 @@ public function it_throw_multi_redirect_exception_on_300_if_no_location(RequestI } }; - $responseRedirect->getStatusCode()->willReturn('300'); + $responseRedirect->getStatusCode()->willReturn(300); $responseRedirect->hasHeader('Location')->willReturn(false); $promise = $this->handleRequest($request, $next, function () {}); @@ -275,7 +275,7 @@ public function it_switch_method_for_302( $request->getUri()->willReturn($uri); $uri->__toString()->willReturn('/original'); - $responseRedirect->getStatusCode()->willReturn('302'); + $responseRedirect->getStatusCode()->willReturn(302); $responseRedirect->hasHeader('Location')->willReturn(true); $responseRedirect->getHeaderLine('Location')->willReturn('/redirect'); @@ -324,7 +324,7 @@ public function it_clears_headers( $request->getUri()->willReturn($uri); $uri->__toString()->willReturn('/original'); - $responseRedirect->getStatusCode()->willReturn('302'); + $responseRedirect->getStatusCode()->willReturn(302); $responseRedirect->hasHeader('Location')->willReturn(true); $responseRedirect->getHeaderLine('Location')->willReturn('/redirect'); @@ -468,7 +468,7 @@ public function it_redirects_http_to_https( ResponseInterface $finalResponse, Promise $promise ) { - $responseRedirect->getStatusCode()->willReturn('302'); + $responseRedirect->getStatusCode()->willReturn(302); $responseRedirect->hasHeader('Location')->willReturn(true); $responseRedirect->getHeaderLine('Location')->willReturn('https://my-site.com/original'); diff --git a/src/Deferred.php b/src/Deferred.php index 7c3f9f5..4c50721 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -79,7 +79,7 @@ public function getState(): string /** * Resolve this deferred with a Response. */ - public function resolve(ResponseInterface $response) + public function resolve(ResponseInterface $response): void { if (self::PENDING !== $this->state) { return; @@ -96,7 +96,7 @@ public function resolve(ResponseInterface $response) /** * Reject this deferred with an Exception. */ - public function reject(Exception $exception) + public function reject(Exception $exception): void { if (self::PENDING !== $this->state) { return; diff --git a/src/Exception/BatchException.php b/src/Exception/BatchException.php index 5167842..46d1e47 100644 --- a/src/Exception/BatchException.php +++ b/src/Exception/BatchException.php @@ -28,10 +28,8 @@ public function __construct(BatchResult $result) /** * Returns the BatchResult that contains all responses and exceptions. - * - * @return BatchResult */ - public function getResult() + public function getResult(): BatchResult { return $this->result; } diff --git a/src/HttpClientPool.php b/src/HttpClientPool.php index 9e979eb..24ab421 100644 --- a/src/HttpClientPool.php +++ b/src/HttpClientPool.php @@ -20,5 +20,5 @@ interface HttpClientPool extends HttpAsyncClient, HttpClient * * @param ClientInterface|HttpAsyncClient|HttpClientPoolItem $client */ - public function addHttpClient($client); + public function addHttpClient($client): void; } diff --git a/src/HttpClientPool/HttpClientPool.php b/src/HttpClientPool/HttpClientPool.php index 0c417c0..ffa21d2 100644 --- a/src/HttpClientPool/HttpClientPool.php +++ b/src/HttpClientPool/HttpClientPool.php @@ -27,7 +27,7 @@ abstract class HttpClientPool implements HttpClientPoolInterface * * @param ClientInterface|HttpAsyncClient|HttpClientPoolItem $client */ - public function addHttpClient($client) + public function addHttpClient($client): void { if (!$client instanceof HttpClientPoolItem) { $client = new HttpClientPoolItem($client); diff --git a/src/HttpClientPool/HttpClientPoolItem.php b/src/HttpClientPool/HttpClientPoolItem.php index 47c9eb2..31ff569 100644 --- a/src/HttpClientPool/HttpClientPoolItem.php +++ b/src/HttpClientPool/HttpClientPoolItem.php @@ -144,7 +144,7 @@ public function getSendingRequestCount(): int /** * Increment the request count. */ - private function incrementRequestCount() + private function incrementRequestCount(): void { ++$this->sendingRequestCount; } @@ -152,7 +152,7 @@ private function incrementRequestCount() /** * Decrement the request count. */ - private function decrementRequestCount() + private function decrementRequestCount(): void { --$this->sendingRequestCount; } @@ -160,7 +160,7 @@ private function decrementRequestCount() /** * Enable the current client. */ - private function enable() + private function enable(): void { $this->disabledAt = null; } @@ -168,7 +168,7 @@ private function enable() /** * Disable the current client. */ - private function disable() + private function disable(): void { $this->disabledAt = new \DateTime('now'); } diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index ccd4a1c..971597b 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -45,7 +45,7 @@ public function sendAsyncRequest(RequestInterface $request) * * @param HttpClient|HttpAsyncClient $client */ - public function addClient($client, RequestMatcher $requestMatcher) + public function addClient($client, RequestMatcher $requestMatcher): void { $this->clients[] = [ 'matcher' => $requestMatcher, diff --git a/src/HttpClientRouterInterface.php b/src/HttpClientRouterInterface.php index 4526e7f..ae012cf 100644 --- a/src/HttpClientRouterInterface.php +++ b/src/HttpClientRouterInterface.php @@ -23,5 +23,5 @@ interface HttpClientRouterInterface extends HttpClient, HttpAsyncClient * * @param ClientInterface|HttpAsyncClient $client */ - public function addClient($client, RequestMatcher $requestMatcher); + public function addClient($client, RequestMatcher $requestMatcher): void; } diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index 9c53c71..9db9363 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -71,7 +71,7 @@ public function options($uri, array $headers = [], $body = null): ResponseInterf return $this->send('OPTIONS', $uri, $headers, $body); } - public function send($method, $uri, array $headers = [], $body = null): ResponseInterface + public function send(string $method, $uri, array $headers = [], $body = null): ResponseInterface { return $this->sendRequest($this->requestFactory->createRequest( $method, diff --git a/src/HttpMethodsClientInterface.php b/src/HttpMethodsClientInterface.php index 20fbf03..bc0829a 100644 --- a/src/HttpMethodsClientInterface.php +++ b/src/HttpMethodsClientInterface.php @@ -112,5 +112,5 @@ public function options($uri, array $headers = [], $body = null): ResponseInterf * * @throws Exception */ - public function send($method, $uri, array $headers = [], $body = null): ResponseInterface; + public function send(string $method, $uri, array $headers = [], $body = null): ResponseInterface; } diff --git a/src/Plugin/AddHostPlugin.php b/src/Plugin/AddHostPlugin.php index db89d34..33ede17 100644 --- a/src/Plugin/AddHostPlugin.php +++ b/src/Plugin/AddHostPlugin.php @@ -66,7 +66,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl return $next($request); } - private function configureOptions(OptionsResolver $resolver) + private function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'replace' => false, diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index 8399210..2ec5265 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -91,11 +91,9 @@ public function handleRequest(RequestInterface $request, callable $next, callabl /** * Creates a cookie from a string. * - * @return Cookie|null - * * @throws TransferException */ - private function createCookie(RequestInterface $request, string $setCookieHeader) + private function createCookie(RequestInterface $request, string $setCookieHeader): ?Cookie { $parts = array_map('trim', explode(';', $setCookieHeader)); @@ -168,6 +166,8 @@ private function createCookie(RequestInterface $request, string $setCookieHeader * Separates key/value pair from cookie. * * @param string $part A single cookie value in format key=value + * + * @return string[] */ private function createValueKey(string $part): array { diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index e562553..061f35b 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -9,7 +9,6 @@ use Http\Client\Common\Plugin; use Http\Client\Exception\HttpException; use Http\Promise\Promise; -use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; @@ -98,7 +97,7 @@ final class RedirectPlugin implements Plugin private $useDefaultForMultiple; /** - * @var array + * @var string[][] Chain identifier => list of URLs for this chain */ private $circularDetection = []; @@ -180,63 +179,51 @@ public function handleRequest(RequestInterface $request, callable $next, callabl }); } - /** - * Builds the redirect request. - * - * @param RequestInterface $request Original request - * @param UriInterface $uri New uri - * @param int $statusCode Status code from the redirect response - * - * @return MessageInterface|RequestInterface - */ - private function buildRedirectRequest(RequestInterface $request, UriInterface $uri, $statusCode) + private function buildRedirectRequest(RequestInterface $originalRequest, UriInterface $targetUri, int $statusCode): RequestInterface { - $request = $request->withUri($uri); + $originalRequest = $originalRequest->withUri($targetUri); - if (false !== $this->redirectCodes[$statusCode]['switch'] && !in_array($request->getMethod(), $this->redirectCodes[$statusCode]['switch']['unless'])) { - $request = $request->withMethod($this->redirectCodes[$statusCode]['switch']['to']); + if (false !== $this->redirectCodes[$statusCode]['switch'] && !in_array($originalRequest->getMethod(), $this->redirectCodes[$statusCode]['switch']['unless'])) { + $originalRequest = $originalRequest->withMethod($this->redirectCodes[$statusCode]['switch']['to']); } if (is_array($this->preserveHeader)) { - $headers = array_keys($request->getHeaders()); + $headers = array_keys($originalRequest->getHeaders()); foreach ($headers as $name) { if (!in_array($name, $this->preserveHeader)) { - $request = $request->withoutHeader($name); + $originalRequest = $originalRequest->withoutHeader($name); } } } - return $request; + return $originalRequest; } /** * Creates a new Uri from the old request and the location header. * - * @param ResponseInterface $response The redirect response - * @param RequestInterface $request The original request - * * @throws HttpException If location header is not usable (missing or incorrect) * @throws MultipleRedirectionException If a 300 status code is received and default location cannot be resolved (doesn't use the location header or not present) */ - private function createUri(ResponseInterface $response, RequestInterface $request): UriInterface + private function createUri(ResponseInterface $redirectResponse, RequestInterface $originalRequest): UriInterface { - if ($this->redirectCodes[$response->getStatusCode()]['multiple'] && (!$this->useDefaultForMultiple || !$response->hasHeader('Location'))) { - throw new MultipleRedirectionException('Cannot choose a redirection', $request, $response); + if ($this->redirectCodes[$redirectResponse->getStatusCode()]['multiple'] && (!$this->useDefaultForMultiple || !$redirectResponse->hasHeader('Location'))) { + throw new MultipleRedirectionException('Cannot choose a redirection', $originalRequest, $redirectResponse); } - if (!$response->hasHeader('Location')) { - throw new HttpException('Redirect status code, but no location header present in the response', $request, $response); + if (!$redirectResponse->hasHeader('Location')) { + throw new HttpException('Redirect status code, but no location header present in the response', $originalRequest, $redirectResponse); } - $location = $response->getHeaderLine('Location'); + $location = $redirectResponse->getHeaderLine('Location'); $parsedLocation = parse_url($location); if (false === $parsedLocation) { - throw new HttpException(sprintf('Location %s could not be parsed', $location), $request, $response); + throw new HttpException(sprintf('Location %s could not be parsed', $location), $originalRequest, $redirectResponse); } - $uri = $request->getUri(); + $uri = $originalRequest->getUri(); if (array_key_exists('scheme', $parsedLocation)) { $uri = $uri->withScheme($parsedLocation['scheme']); From afa7836c43221352fc2691a6d882d5ff59f8bdf2 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sun, 6 Jan 2019 10:10:57 +0100 Subject: [PATCH 039/123] Update ErrorPlugin.php --- src/Plugin/ErrorPlugin.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Plugin/ErrorPlugin.php b/src/Plugin/ErrorPlugin.php index 2d1d2f1..a187295 100644 --- a/src/Plugin/ErrorPlugin.php +++ b/src/Plugin/ErrorPlugin.php @@ -17,6 +17,14 @@ * * Status codes 400-499 lead to a ClientErrorException, status 500-599 to a ServerErrorException. * + * Warning + * ======= + * + * Throwing an exception on a valid response violates the PSR-18 specification. + * This plugin is provided as a convenience when writing a small application. + * When providing a client to a third party library, this plugin must not be + * included, or the third party library will have problems with error handling. + * * @author Joel Wurtz */ final class ErrorPlugin implements Plugin From 122ff53d060df5c9495aa6b9d7209815da1133ca Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sun, 20 Jan 2019 08:40:38 +0100 Subject: [PATCH 040/123] typehint psr exceptions (#161) * typehint psr exceptions * cs --- src/BatchClient.php | 13 +++---------- src/BatchClientInterface.php | 3 +-- src/BatchResult.php | 8 ++++---- src/HttpClientPool/HttpClientPoolItem.php | 2 +- src/HttpMethodsClient.php | 4 ---- src/Plugin/RequestMatcherPlugin.php | 4 ++-- 6 files changed, 11 insertions(+), 23 deletions(-) diff --git a/src/BatchClient.php b/src/BatchClient.php index bd340af..51f13ff 100644 --- a/src/BatchClient.php +++ b/src/BatchClient.php @@ -4,11 +4,9 @@ namespace Http\Client\Common; -use Http\Client\Exception; use Http\Client\Common\Exception\BatchException; +use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; final class BatchClient implements BatchClientInterface { @@ -22,20 +20,15 @@ public function __construct(ClientInterface $client) $this->client = $client; } - public function sendRequest(RequestInterface $request): ResponseInterface - { - return $this->client->sendRequest($request); - } - public function sendRequests(array $requests): BatchResult { $batchResult = new BatchResult(); foreach ($requests as $request) { try { - $response = $this->sendRequest($request); + $response = $this->client->sendRequest($request); $batchResult = $batchResult->addResponse($request, $response); - } catch (Exception $e) { + } catch (ClientExceptionInterface $e) { $batchResult = $batchResult->addException($request, $e); } } diff --git a/src/BatchClientInterface.php b/src/BatchClientInterface.php index 56a34ae..4fc70e8 100644 --- a/src/BatchClientInterface.php +++ b/src/BatchClientInterface.php @@ -4,7 +4,6 @@ namespace Http\Client\Common; -use Http\Client\HttpClient; use Http\Client\Common\Exception\BatchException; use Psr\Http\Message\RequestInterface; @@ -15,7 +14,7 @@ * * @author Joel Wurtz */ -interface BatchClientInterface extends HttpClient +interface BatchClientInterface { /** * Send several requests. diff --git a/src/BatchResult.php b/src/BatchResult.php index 4202c70..f353833 100644 --- a/src/BatchResult.php +++ b/src/BatchResult.php @@ -4,7 +4,7 @@ namespace Http\Client\Common; -use Http\Client\Exception; +use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -102,7 +102,7 @@ public function hasExceptions(): bool /** * Returns all exceptions for the unsuccessful requests. * - * @return Exception[] + * @return ClientExceptionInterface[] */ public function getExceptions(): array { @@ -129,7 +129,7 @@ public function isFailed(RequestInterface $request): bool * * @throws \UnexpectedValueException If request was not part of the batch or was successful */ - public function getExceptionFor(RequestInterface $request): Exception + public function getExceptionFor(RequestInterface $request): ClientExceptionInterface { try { return $this->exceptions[$request]; @@ -143,7 +143,7 @@ public function getExceptionFor(RequestInterface $request): Exception * * @return BatchResult the new BatchResult with this request-exception pair added to it */ - public function addException(RequestInterface $request, Exception $exception): self + public function addException(RequestInterface $request, ClientExceptionInterface $exception): self { $new = clone $this; $new->exceptions->attach($request, $exception); diff --git a/src/HttpClientPool/HttpClientPoolItem.php b/src/HttpClientPool/HttpClientPoolItem.php index 31ff569..740262a 100644 --- a/src/HttpClientPool/HttpClientPoolItem.php +++ b/src/HttpClientPool/HttpClientPoolItem.php @@ -55,7 +55,7 @@ class HttpClientPoolItem implements HttpClient, HttpAsyncClient /** * @param ClientInterface|HttpAsyncClient $client - * @param null|int $reenableAfter Number of seconds until this client is enabled again after an error + * @param int|null $reenableAfter Number of seconds until this client is enabled again after an error */ public function __construct($client, $reenableAfter = null) { diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index 9db9363..5b49c7c 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -21,10 +21,6 @@ final class HttpMethodsClient implements HttpMethodsClientInterface */ private $requestFactory; - /** - * @param ClientInterface $httpClient The client to send requests with - * @param RequestFactory $requestFactory The message factory to create requests - */ public function __construct(ClientInterface $httpClient, RequestFactory $requestFactory) { $this->httpClient = $httpClient; diff --git a/src/Plugin/RequestMatcherPlugin.php b/src/Plugin/RequestMatcherPlugin.php index 3a3ff8f..45d4375 100644 --- a/src/Plugin/RequestMatcherPlugin.php +++ b/src/Plugin/RequestMatcherPlugin.php @@ -22,12 +22,12 @@ final class RequestMatcherPlugin implements Plugin private $requestMatcher; /** - * @var null|Plugin + * @var Plugin|null */ private $successPlugin; /** - * @var null|Plugin + * @var Plugin|null */ private $failurePlugin; From 9de3ec8a3d743ce37f2d27e26516df198025daef Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 20 Jan 2019 08:40:10 +0100 Subject: [PATCH 041/123] Minor cleanup --- src/Deferred.php | 19 +++++++------------ src/HttpClientPool/HttpClientPoolItem.php | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Deferred.php b/src/Deferred.php index 4c50721..022213d 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -14,15 +14,10 @@ final class Deferred implements Promise { private $value; - private $failure; - private $state; - private $waitCallback; - private $onFulfilledCallbacks; - private $onRejectedCallbacks; public function __construct(callable $waitCallback) @@ -81,12 +76,12 @@ public function getState(): string */ public function resolve(ResponseInterface $response): void { - if (self::PENDING !== $this->state) { + if (Promise::PENDING !== $this->state) { return; } $this->value = $response; - $this->state = self::FULFILLED; + $this->state = Promise::FULFILLED; foreach ($this->onFulfilledCallbacks as $onFulfilledCallback) { $onFulfilledCallback($response); @@ -98,12 +93,12 @@ public function resolve(ResponseInterface $response): void */ public function reject(Exception $exception): void { - if (self::PENDING !== $this->state) { + if (Promise::PENDING !== $this->state) { return; } $this->failure = $exception; - $this->state = self::REJECTED; + $this->state = Promise::REJECTED; foreach ($this->onRejectedCallbacks as $onRejectedCallback) { $onRejectedCallback($exception); @@ -115,16 +110,16 @@ public function reject(Exception $exception): void */ public function wait($unwrap = true) { - if (self::PENDING === $this->state) { + if (Promise::PENDING === $this->state) { $callback = $this->waitCallback; $callback(); } if (!$unwrap) { - return; + return null; } - if (self::FULFILLED === $this->state) { + if (Promise::FULFILLED === $this->state) { return $this->value; } diff --git a/src/HttpClientPool/HttpClientPoolItem.php b/src/HttpClientPool/HttpClientPoolItem.php index 740262a..2c000ba 100644 --- a/src/HttpClientPool/HttpClientPoolItem.php +++ b/src/HttpClientPool/HttpClientPoolItem.php @@ -57,7 +57,7 @@ class HttpClientPoolItem implements HttpClient, HttpAsyncClient * @param ClientInterface|HttpAsyncClient $client * @param int|null $reenableAfter Number of seconds until this client is enabled again after an error */ - public function __construct($client, $reenableAfter = null) + public function __construct($client, int $reenableAfter = null) { $this->client = new FlexibleHttpClient($client); $this->reenableAfter = $reenableAfter; From f5143fe5a5273f4aca6491bcd5dffd69e36df2ce Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 20 Jan 2019 10:08:21 +0100 Subject: [PATCH 042/123] Added doc blocks --- src/Deferred.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Deferred.php b/src/Deferred.php index 022213d..716c12c 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -13,11 +13,34 @@ */ final class Deferred implements Promise { + /** + * @var ResponseInterface|null + */ private $value; + + /** + * @var Exception|null + */ private $failure; + + /** + * @var string + */ private $state; + + /** + * @var callable + */ private $waitCallback; + + /** + * @var callable[] + */ private $onFulfilledCallbacks; + + /** + * @var callable[] + */ private $onRejectedCallbacks; public function __construct(callable $waitCallback) From 44bf9e394da46b9ab61e5eb1ebcefc33397173d2 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 20 Jan 2019 12:59:13 +0100 Subject: [PATCH 043/123] Make sure we handle PSR-18 exceptions --- src/Deferred.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Deferred.php b/src/Deferred.php index 716c12c..02fcc29 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -4,8 +4,8 @@ namespace Http\Client\Common; -use Http\Client\Exception; use Http\Promise\Promise; +use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Message\ResponseInterface; /** @@ -19,7 +19,7 @@ final class Deferred implements Promise private $value; /** - * @var Exception|null + * @var ClientExceptionInterface|null */ private $failure; @@ -64,12 +64,12 @@ public function then(callable $onFulfilled = null, callable $onRejected = null): $response = $onFulfilled($response); } $deferred->resolve($response); - } catch (Exception $exception) { + } catch (ClientExceptionInterface $exception) { $deferred->reject($exception); } }; - $this->onRejectedCallbacks[] = function (Exception $exception) use ($onRejected, $deferred) { + $this->onRejectedCallbacks[] = function (ClientExceptionInterface $exception) use ($onRejected, $deferred) { try { if (null !== $onRejected) { $response = $onRejected($exception); @@ -78,7 +78,7 @@ public function then(callable $onFulfilled = null, callable $onRejected = null): return; } $deferred->reject($exception); - } catch (Exception $newException) { + } catch (ClientExceptionInterface $newException) { $deferred->reject($newException); } }; @@ -114,7 +114,7 @@ public function resolve(ResponseInterface $response): void /** * Reject this deferred with an Exception. */ - public function reject(Exception $exception): void + public function reject(ClientExceptionInterface $exception): void { if (Promise::PENDING !== $this->state) { return; From 68713627e85434c1a20529e8c108ce0c0938950b Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 20 Jan 2019 13:04:15 +0100 Subject: [PATCH 044/123] Updated the exceptions we are using --- src/Plugin/HistoryPlugin.php | 4 ++-- src/Plugin/Journal.php | 8 ++++---- src/Plugin/RetryPlugin.php | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Plugin/HistoryPlugin.php b/src/Plugin/HistoryPlugin.php index a147d2e..a1796a6 100644 --- a/src/Plugin/HistoryPlugin.php +++ b/src/Plugin/HistoryPlugin.php @@ -5,8 +5,8 @@ namespace Http\Client\Common\Plugin; use Http\Client\Common\Plugin; -use Http\Client\Exception; use Http\Promise\Promise; +use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -40,7 +40,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl $journal->addSuccess($request, $response); return $response; - }, function (Exception $exception) use ($request, $journal) { + }, function (ClientExceptionInterface $exception) use ($request, $journal) { $journal->addFailure($request, $exception); throw $exception; diff --git a/src/Plugin/Journal.php b/src/Plugin/Journal.php index 46f0a7b..9faa938 100644 --- a/src/Plugin/Journal.php +++ b/src/Plugin/Journal.php @@ -4,7 +4,7 @@ namespace Http\Client\Common\Plugin; -use Http\Client\Exception; +use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -26,8 +26,8 @@ public function addSuccess(RequestInterface $request, ResponseInterface $respons /** * Record a failed call. * - * @param RequestInterface $request Request use to make the call - * @param Exception $exception Exception returned by the call + * @param RequestInterface $request Request use to make the call + * @param ClientExceptionInterface $exception Exception returned by the call */ - public function addFailure(RequestInterface $request, Exception $exception); + public function addFailure(RequestInterface $request, ClientExceptionInterface $exception); } diff --git a/src/Plugin/RetryPlugin.php b/src/Plugin/RetryPlugin.php index d13e71e..92b7cbf 100644 --- a/src/Plugin/RetryPlugin.php +++ b/src/Plugin/RetryPlugin.php @@ -5,9 +5,9 @@ namespace Http\Client\Common\Plugin; use Http\Client\Common\Plugin; -use Http\Client\Exception; use Http\Client\Exception\HttpException; use Http\Promise\Promise; +use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -74,7 +74,7 @@ public function __construct(array $config = []) // do not retry client errors return $response->getStatusCode() >= 500 && $response->getStatusCode() < 600; }, - 'exception_decider' => function (RequestInterface $request, Exception $e) { + 'exception_decider' => function (RequestInterface $request, ClientExceptionInterface $e) { // do not retry client errors return !$e instanceof HttpException || $e->getCode() >= 500 && $e->getCode() < 600; }, @@ -124,7 +124,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl } return $response; - }, function (Exception $exception) use ($request, $next, $first, $chainIdentifier) { + }, function (ClientExceptionInterface $exception) use ($request, $next, $first, $chainIdentifier) { if (!array_key_exists($chainIdentifier, $this->retryStorage)) { $this->retryStorage[$chainIdentifier] = 0; } @@ -156,7 +156,7 @@ public static function defaultErrorResponseDelay(RequestInterface $request, Resp /** * @param int $retries The number of retries we made before. First time this get called it will be 0. */ - public static function defaultExceptionDelay(RequestInterface $request, Exception $e, int $retries): int + public static function defaultExceptionDelay(RequestInterface $request, ClientExceptionInterface $e, int $retries): int { return pow(2, $retries) * 500000; } From 91608c418f612bc941303341d168d97c38fede8a Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 20 Jan 2019 13:08:59 +0100 Subject: [PATCH 045/123] Use class constants --- src/FlexibleHttpClient.php | 2 +- src/PluginClient.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FlexibleHttpClient.php b/src/FlexibleHttpClient.php index 3979868..d6f1862 100644 --- a/src/FlexibleHttpClient.php +++ b/src/FlexibleHttpClient.php @@ -25,7 +25,7 @@ final class FlexibleHttpClient implements HttpClient, HttpAsyncClient public function __construct($client) { if (!($client instanceof ClientInterface) && !($client instanceof HttpAsyncClient)) { - throw new \LogicException('Client must be an instance of Psr\\Http\\Client\\ClientInterface or Http\\Client\\HttpAsyncClient'); + throw new \LogicException(sprintf('Client must be an instance of %s or %s', ClientInterface::class, HttpAsyncClient::class)); } $this->httpClient = $client; diff --git a/src/PluginClient.php b/src/PluginClient.php index 696d3ef..a3a4023 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -60,7 +60,7 @@ public function __construct($client, array $plugins = [], array $options = []) } elseif ($client instanceof ClientInterface) { $this->client = new EmulatedHttpAsyncClient($client); } else { - throw new \RuntimeException('Client must be an instance of Psr\\Http\\Client\\ClientInterface or Http\\Client\\HttpAsyncClient'); + throw new \LogicException(sprintf('Client must be an instance of %s or %s', ClientInterface::class, HttpAsyncClient::class)); } $this->plugins = $plugins; From c3cdde77da4bf83f35047f351aed82786a58125f Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Mon, 21 Jan 2019 11:04:41 +0100 Subject: [PATCH 046/123] Make sure we check for PSR client --- src/HttpClientRouter.php | 2 +- src/PluginClient.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index 971597b..588fda1 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -43,7 +43,7 @@ public function sendAsyncRequest(RequestInterface $request) /** * Add a client to the router. * - * @param HttpClient|HttpAsyncClient $client + * @param ClientInterface|HttpAsyncClient $client */ public function addClient($client, RequestMatcher $requestMatcher): void { diff --git a/src/PluginClient.php b/src/PluginClient.php index a3a4023..8288445 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -73,7 +73,7 @@ public function __construct($client, array $plugins = [], array $options = []) public function sendRequest(RequestInterface $request): ResponseInterface { // If we don't have an http client, use the async call - if (!($this->client instanceof HttpClient)) { + if (!($this->client instanceof ClientInterface)) { return $this->sendAsyncRequest($request)->wait(); } From ff76802ff1a61ede821077049ee7488ad30e416a Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Mon, 21 Jan 2019 11:06:18 +0100 Subject: [PATCH 047/123] cs --- src/HttpClientRouter.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index 588fda1..84c7e04 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -6,7 +6,6 @@ use Http\Client\Common\Exception\HttpClientNoMatchException; use Http\Client\HttpAsyncClient; -use Http\Client\HttpClient; use Http\Message\RequestMatcher; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; From 1ede4e085eeaf9307223fc0c8eb48bf26713cc8b Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Mon, 21 Jan 2019 23:13:25 +0100 Subject: [PATCH 048/123] Add always seekable body plugin --- spec/Plugin/AlwaysSeekableBodyPluginSpec.php | 98 ++++++++++++++++++++ src/Plugin/AlwaysSeekableBodyPlugin.php | 65 +++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 spec/Plugin/AlwaysSeekableBodyPluginSpec.php create mode 100644 src/Plugin/AlwaysSeekableBodyPlugin.php diff --git a/spec/Plugin/AlwaysSeekableBodyPluginSpec.php b/spec/Plugin/AlwaysSeekableBodyPluginSpec.php new file mode 100644 index 0000000..cf1ac65 --- /dev/null +++ b/spec/Plugin/AlwaysSeekableBodyPluginSpec.php @@ -0,0 +1,98 @@ +shouldHaveType(AlwaysSeekableBodyPlugin::class); + } + + public function it_is_a_plugin() + { + $this->shouldImplement(Plugin::class); + } + + public function it_decorate_response_body_if_not_seekable(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream) + { + $next = function () use ($response) { + return new HttpFulfilledPromise($response->getWrappedObject()); + }; + + $request->getBody()->shouldBeCalled()->willReturn($requestStream); + $requestStream->isSeekable()->shouldBeCalled()->willReturn(true); + + $response->getBody()->shouldBeCalled()->willReturn($responseStream); + $responseStream->isSeekable()->shouldBeCalled()->willReturn(false); + $responseStream->getSize()->willReturn(null); + + $response->withBody(Argument::type(BufferedStream::class))->shouldBeCalled()->willReturn($response); + + $this->handleRequest($request, $next, function () {}); + } + + public function it_does_not_decorate_response_body_if_seekable(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream) + { + $next = function () use ($response) { + return new HttpFulfilledPromise($response->getWrappedObject()); + }; + + $request->getBody()->shouldBeCalled()->willReturn($requestStream); + $requestStream->isSeekable()->shouldBeCalled()->willReturn(true); + + $response->getBody()->shouldBeCalled()->willReturn($responseStream); + $responseStream->isSeekable()->shouldBeCalled()->willReturn(true); + $responseStream->getSize()->willReturn(null); + + $response->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled(); + + $this->handleRequest($request, $next, function () {}); + } + + public function it_decorate_request_body_if_not_seekable(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream) + { + $next = function () use ($response) { + return new HttpFulfilledPromise($response->getWrappedObject()); + }; + + $response->getBody()->willReturn($responseStream); + $responseStream->isSeekable()->willReturn(true); + + $request->getBody()->shouldBeCalled()->willReturn($requestStream); + $requestStream->isSeekable()->shouldBeCalled()->willReturn(false); + $requestStream->getSize()->willReturn(null); + + $request->withBody(Argument::type(BufferedStream::class))->shouldBeCalled(); + + $this->handleRequest($request, $next, function () {}); + } + + public function it_does_not_decorate_request_body_if_seekable(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream) + { + $next = function () use ($response) { + return new HttpFulfilledPromise($response->getWrappedObject()); + }; + + $response->getBody()->willReturn($responseStream); + $responseStream->isSeekable()->willReturn(true); + + $request->getBody()->shouldBeCalled()->willReturn($requestStream); + $requestStream->isSeekable()->shouldBeCalled()->willReturn(true); + $requestStream->getSize()->willReturn(null); + + $request->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled(); + + $this->handleRequest($request, $next, function () {}); + } +} diff --git a/src/Plugin/AlwaysSeekableBodyPlugin.php b/src/Plugin/AlwaysSeekableBodyPlugin.php new file mode 100644 index 0000000..2af3c61 --- /dev/null +++ b/src/Plugin/AlwaysSeekableBodyPlugin.php @@ -0,0 +1,65 @@ + + */ +final class AlwaysSeekableBodyPlugin implements Plugin +{ + private $useFileBuffer; + + private $memoryBufferSize; + + /** + * @param array $config { + * + * @var bool $use_file_buffer Whether this plugin should use a file as a buffer if the stream is too big, defaults to true + * @var int $memory_buffer_size Max memory size in bytes to use for the buffer before it use a file, defaults to 2097152 (2 mb) + * } + */ + public function __construct(array $config = []) + { + $resolver = new OptionsResolver(); + $resolver->setDefaults([ + 'use_file_buffer' => true, + 'memory_buffer_size' => 2097152 + ]); + $resolver->setAllowedTypes('use_file_buffer', 'bool'); + $resolver->setAllowedTypes('memory_buffer_size', 'int'); + + $options = $resolver->resolve($config); + + $this->useFileBuffer = $options['use_file_buffer']; + $this->memoryBufferSize = $options['memory_buffer_size']; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise + { + if (!$request->getBody()->isSeekable()) { + $request = $request->withBody(new BufferedStream($request->getBody(), $this->useFileBuffer, $this->memoryBufferSize)); + } + + return $next($request)->then(function (ResponseInterface $response) { + if ($response->getBody()->isSeekable()) { + return $response; + } + + return $response->withBody(new BufferedStream($response->getBody(), $this->useFileBuffer, $this->memoryBufferSize)); + }); + } +} From b013db50d654bab0208e9ba4a134b0670cfd0250 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Mon, 21 Jan 2019 23:17:36 +0100 Subject: [PATCH 049/123] Fix cs --- src/Plugin/AlwaysSeekableBodyPlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Plugin/AlwaysSeekableBodyPlugin.php b/src/Plugin/AlwaysSeekableBodyPlugin.php index 2af3c61..57d9d4c 100644 --- a/src/Plugin/AlwaysSeekableBodyPlugin.php +++ b/src/Plugin/AlwaysSeekableBodyPlugin.php @@ -12,7 +12,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; /** - * Decorate the body of the request and the response if it's not seekable by using Http\Message\Stream\BufferedStream + * Decorate the body of the request and the response if it's not seekable by using Http\Message\Stream\BufferedStream. * * @author Joel Wurtz */ @@ -34,7 +34,7 @@ public function __construct(array $config = []) $resolver = new OptionsResolver(); $resolver->setDefaults([ 'use_file_buffer' => true, - 'memory_buffer_size' => 2097152 + 'memory_buffer_size' => 2097152, ]); $resolver->setAllowedTypes('use_file_buffer', 'bool'); $resolver->setAllowedTypes('memory_buffer_size', 'int'); From a3c421cb79fb15da67c09241298fa4cfaec14476 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 29 Jan 2019 15:59:34 +0100 Subject: [PATCH 050/123] Split in two separate plugin (to handle request / response at different time) --- spec/Plugin/AlwaysSeekableBodyPluginSpec.php | 98 ------------------- spec/Plugin/RequestSeekableBodyPluginSpec.php | 46 +++++++++ .../Plugin/ResponseSeekableBodyPluginSpec.php | 56 +++++++++++ src/Plugin/AlwaysSeekableBodyPlugin.php | 65 ------------ src/Plugin/RequestSeekableBodyPlugin.php | 29 ++++++ src/Plugin/ResponseSeekableBodyPlugin.php | 32 ++++++ src/Plugin/SeekableBodyPlugin.php | 41 ++++++++ 7 files changed, 204 insertions(+), 163 deletions(-) delete mode 100644 spec/Plugin/AlwaysSeekableBodyPluginSpec.php create mode 100644 spec/Plugin/RequestSeekableBodyPluginSpec.php create mode 100644 spec/Plugin/ResponseSeekableBodyPluginSpec.php delete mode 100644 src/Plugin/AlwaysSeekableBodyPlugin.php create mode 100644 src/Plugin/RequestSeekableBodyPlugin.php create mode 100644 src/Plugin/ResponseSeekableBodyPlugin.php create mode 100644 src/Plugin/SeekableBodyPlugin.php diff --git a/spec/Plugin/AlwaysSeekableBodyPluginSpec.php b/spec/Plugin/AlwaysSeekableBodyPluginSpec.php deleted file mode 100644 index cf1ac65..0000000 --- a/spec/Plugin/AlwaysSeekableBodyPluginSpec.php +++ /dev/null @@ -1,98 +0,0 @@ -shouldHaveType(AlwaysSeekableBodyPlugin::class); - } - - public function it_is_a_plugin() - { - $this->shouldImplement(Plugin::class); - } - - public function it_decorate_response_body_if_not_seekable(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream) - { - $next = function () use ($response) { - return new HttpFulfilledPromise($response->getWrappedObject()); - }; - - $request->getBody()->shouldBeCalled()->willReturn($requestStream); - $requestStream->isSeekable()->shouldBeCalled()->willReturn(true); - - $response->getBody()->shouldBeCalled()->willReturn($responseStream); - $responseStream->isSeekable()->shouldBeCalled()->willReturn(false); - $responseStream->getSize()->willReturn(null); - - $response->withBody(Argument::type(BufferedStream::class))->shouldBeCalled()->willReturn($response); - - $this->handleRequest($request, $next, function () {}); - } - - public function it_does_not_decorate_response_body_if_seekable(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream) - { - $next = function () use ($response) { - return new HttpFulfilledPromise($response->getWrappedObject()); - }; - - $request->getBody()->shouldBeCalled()->willReturn($requestStream); - $requestStream->isSeekable()->shouldBeCalled()->willReturn(true); - - $response->getBody()->shouldBeCalled()->willReturn($responseStream); - $responseStream->isSeekable()->shouldBeCalled()->willReturn(true); - $responseStream->getSize()->willReturn(null); - - $response->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled(); - - $this->handleRequest($request, $next, function () {}); - } - - public function it_decorate_request_body_if_not_seekable(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream) - { - $next = function () use ($response) { - return new HttpFulfilledPromise($response->getWrappedObject()); - }; - - $response->getBody()->willReturn($responseStream); - $responseStream->isSeekable()->willReturn(true); - - $request->getBody()->shouldBeCalled()->willReturn($requestStream); - $requestStream->isSeekable()->shouldBeCalled()->willReturn(false); - $requestStream->getSize()->willReturn(null); - - $request->withBody(Argument::type(BufferedStream::class))->shouldBeCalled(); - - $this->handleRequest($request, $next, function () {}); - } - - public function it_does_not_decorate_request_body_if_seekable(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream) - { - $next = function () use ($response) { - return new HttpFulfilledPromise($response->getWrappedObject()); - }; - - $response->getBody()->willReturn($responseStream); - $responseStream->isSeekable()->willReturn(true); - - $request->getBody()->shouldBeCalled()->willReturn($requestStream); - $requestStream->isSeekable()->shouldBeCalled()->willReturn(true); - $requestStream->getSize()->willReturn(null); - - $request->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled(); - - $this->handleRequest($request, $next, function () {}); - } -} diff --git a/spec/Plugin/RequestSeekableBodyPluginSpec.php b/spec/Plugin/RequestSeekableBodyPluginSpec.php new file mode 100644 index 0000000..fbb5530 --- /dev/null +++ b/spec/Plugin/RequestSeekableBodyPluginSpec.php @@ -0,0 +1,46 @@ +shouldHaveType(RequestSeekableBodyPlugin::class); + } + + public function it_is_a_plugin() + { + $this->shouldImplement(Plugin::class); + } + + public function it_decorate_request_body_if_not_seekable(RequestInterface $request, StreamInterface $requestStream) + { + $request->getBody()->shouldBeCalled()->willReturn($requestStream); + $requestStream->isSeekable()->shouldBeCalled()->willReturn(false); + $requestStream->getSize()->willReturn(null); + + $request->withBody(Argument::type(BufferedStream::class))->shouldBeCalled()->willReturn($request); + + $this->handleRequest($request, PluginStub::next(), function () {}); + } + + public function it_does_not_decorate_request_body_if_seekable(RequestInterface $request, StreamInterface $requestStream) + { + $request->getBody()->shouldBeCalled()->willReturn($requestStream); + $requestStream->isSeekable()->shouldBeCalled()->willReturn(true); + $requestStream->getSize()->willReturn(null); + + $request->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled(); + + $this->handleRequest($request, PluginStub::next(), function () {}); + } +} diff --git a/spec/Plugin/ResponseSeekableBodyPluginSpec.php b/spec/Plugin/ResponseSeekableBodyPluginSpec.php new file mode 100644 index 0000000..4f75027 --- /dev/null +++ b/spec/Plugin/ResponseSeekableBodyPluginSpec.php @@ -0,0 +1,56 @@ +shouldHaveType(ResponseSeekableBodyPlugin::class); + } + + public function it_is_a_plugin() + { + $this->shouldImplement(Plugin::class); + } + + public function it_decorate_response_body_if_not_seekable(ResponseInterface $response, StreamInterface $responseStream) + { + $next = function () use ($response) { + return new HttpFulfilledPromise($response->getWrappedObject()); + }; + + $response->getBody()->shouldBeCalled()->willReturn($responseStream); + $responseStream->isSeekable()->shouldBeCalled()->willReturn(false); + $responseStream->getSize()->willReturn(null); + + $response->withBody(Argument::type(BufferedStream::class))->shouldBeCalled()->willReturn($response); + + $this->handleRequest(new Request('GET', '/'), $next, function () {}); + } + + public function it_does_not_decorate_response_body_if_seekable(ResponseInterface $response, StreamInterface $responseStream) + { + $next = function () use ($response) { + return new HttpFulfilledPromise($response->getWrappedObject()); + }; + + $response->getBody()->shouldBeCalled()->willReturn($responseStream); + $responseStream->isSeekable()->shouldBeCalled()->willReturn(true); + $responseStream->getSize()->willReturn(null); + + $response->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled(); + + $this->handleRequest(new Request('GET', '/'), $next, function () {}); + } +} diff --git a/src/Plugin/AlwaysSeekableBodyPlugin.php b/src/Plugin/AlwaysSeekableBodyPlugin.php deleted file mode 100644 index 57d9d4c..0000000 --- a/src/Plugin/AlwaysSeekableBodyPlugin.php +++ /dev/null @@ -1,65 +0,0 @@ - - */ -final class AlwaysSeekableBodyPlugin implements Plugin -{ - private $useFileBuffer; - - private $memoryBufferSize; - - /** - * @param array $config { - * - * @var bool $use_file_buffer Whether this plugin should use a file as a buffer if the stream is too big, defaults to true - * @var int $memory_buffer_size Max memory size in bytes to use for the buffer before it use a file, defaults to 2097152 (2 mb) - * } - */ - public function __construct(array $config = []) - { - $resolver = new OptionsResolver(); - $resolver->setDefaults([ - 'use_file_buffer' => true, - 'memory_buffer_size' => 2097152, - ]); - $resolver->setAllowedTypes('use_file_buffer', 'bool'); - $resolver->setAllowedTypes('memory_buffer_size', 'int'); - - $options = $resolver->resolve($config); - - $this->useFileBuffer = $options['use_file_buffer']; - $this->memoryBufferSize = $options['memory_buffer_size']; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise - { - if (!$request->getBody()->isSeekable()) { - $request = $request->withBody(new BufferedStream($request->getBody(), $this->useFileBuffer, $this->memoryBufferSize)); - } - - return $next($request)->then(function (ResponseInterface $response) { - if ($response->getBody()->isSeekable()) { - return $response; - } - - return $response->withBody(new BufferedStream($response->getBody(), $this->useFileBuffer, $this->memoryBufferSize)); - }); - } -} diff --git a/src/Plugin/RequestSeekableBodyPlugin.php b/src/Plugin/RequestSeekableBodyPlugin.php new file mode 100644 index 0000000..1b6c528 --- /dev/null +++ b/src/Plugin/RequestSeekableBodyPlugin.php @@ -0,0 +1,29 @@ + + */ +final class RequestSeekableBodyPlugin extends SeekableBodyPlugin +{ + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise + { + if (!$request->getBody()->isSeekable()) { + $request = $request->withBody(new BufferedStream($request->getBody(), $this->useFileBuffer, $this->memoryBufferSize)); + } + + return $next($request); + } +} diff --git a/src/Plugin/ResponseSeekableBodyPlugin.php b/src/Plugin/ResponseSeekableBodyPlugin.php new file mode 100644 index 0000000..6f941b6 --- /dev/null +++ b/src/Plugin/ResponseSeekableBodyPlugin.php @@ -0,0 +1,32 @@ + + */ +final class ResponseSeekableBodyPlugin extends SeekableBodyPlugin +{ + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise + { + return $next($request)->then(function (ResponseInterface $response) { + if ($response->getBody()->isSeekable()) { + return $response; + } + + return $response->withBody(new BufferedStream($response->getBody(), $this->useFileBuffer, $this->memoryBufferSize)); + }); + } +} diff --git a/src/Plugin/SeekableBodyPlugin.php b/src/Plugin/SeekableBodyPlugin.php new file mode 100644 index 0000000..3b37b1c --- /dev/null +++ b/src/Plugin/SeekableBodyPlugin.php @@ -0,0 +1,41 @@ +setDefaults([ + 'use_file_buffer' => true, + 'memory_buffer_size' => 2097152, + ]); + $resolver->setAllowedTypes('use_file_buffer', 'bool'); + $resolver->setAllowedTypes('memory_buffer_size', 'int'); + + $options = $resolver->resolve($config); + + $this->useFileBuffer = $options['use_file_buffer']; + $this->memoryBufferSize = $options['memory_buffer_size']; + } +} From c1ffd29e316243b5834504b87c6d9ea076b371be Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Sat, 2 Feb 2019 10:55:20 +0100 Subject: [PATCH 051/123] Prepare for 1.9.1 (#169) --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a45fcba..169b8a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,12 @@ - Deprecated option `debug_plugins` has been removed from `PluginClient` - Deprecated options `decider` and `delay` have been removed from `RetryPlugin`, use `exception_decider` and `exception_delay` instead. +## 1.9.1 - 2019-02-02 + +### Added + +- Updated type hints in doc blocks. + ## 1.9.0 - 2019-01-03 ### Added From 2b8aa3c4910afc21146a9c8f96adb266e869517a Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Sun, 3 Feb 2019 17:49:09 +0100 Subject: [PATCH 052/123] Update date in changelog (#166) * Update date in changelog * minor * minor * minor cleanup --- CHANGELOG.md | 4 ++-- src/BatchClientInterface.php | 2 +- src/Exception/BatchException.php | 1 + src/Plugin/ContentTypePlugin.php | 3 --- src/PluginClient.php | 2 +- src/PluginClientFactory.php | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 169b8a8..36b7bc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 2.0 (unreleased) +## 2.0.0 - 2019-02-03 ### Changed - HttpClientRouter now throws a HttpClientNoMatchException instead of a RequestException if it can not find a client for the request. @@ -10,7 +10,7 @@ - Abstract method `HttpClientPool::chooseHttpClient()` has now an explicit return type (`Http\Client\Common\HttpClientPoolItem`) - Interface method `Plugin::handleRequest(...)` has now an explicit return type (`Http\Promise\Promise`) - Made classes final that are not intended to be extended. - Added interfaces for BatchClient, HttpClientRouter and HttpMethodsClient. +- Added interfaces for BatchClient, HttpClientRouter and HttpMethodsClient. (These interfaces use the `Interface` suffix to avoid name collisions.) - Added an interface for HttpClientPool and moved the abstract class to the HttpClientPool sub namespace. - AddPathPlugin: Do not add the prefix if the URL already has the same prefix. diff --git a/src/BatchClientInterface.php b/src/BatchClientInterface.php index 4fc70e8..d42eb2d 100644 --- a/src/BatchClientInterface.php +++ b/src/BatchClientInterface.php @@ -22,7 +22,7 @@ interface BatchClientInterface * You may not assume that the requests are executed in a particular order. If the order matters * for your application, use sendRequest sequentially. * - * @param RequestInterface[] The requests to send + * @param RequestInterface[] $requests The requests to send * * @return BatchResult Containing one result per request * diff --git a/src/Exception/BatchException.php b/src/Exception/BatchException.php index 46d1e47..cbda665 100644 --- a/src/Exception/BatchException.php +++ b/src/Exception/BatchException.php @@ -24,6 +24,7 @@ final class BatchException extends TransferException public function __construct(BatchResult $result) { $this->result = $result; + parent::__construct(); } /** diff --git a/src/Plugin/ContentTypePlugin.php b/src/Plugin/ContentTypePlugin.php index f3944dd..c3fb25c 100644 --- a/src/Plugin/ContentTypePlugin.php +++ b/src/Plugin/ContentTypePlugin.php @@ -106,9 +106,6 @@ private function isJson(StreamInterface $stream): bool return JSON_ERROR_NONE === json_last_error(); } - /** - * @param $stream StreamInterface - */ private function isXml(StreamInterface $stream): bool { if (!function_exists('simplexml_load_string')) { diff --git a/src/PluginClient.php b/src/PluginClient.php index 8288445..7a44aab 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -25,7 +25,7 @@ final class PluginClient implements HttpClient, HttpAsyncClient /** * An HTTP async client. * - * @var HttpAsyncClient + * @var HttpAsyncClient|HttpClient */ private $client; diff --git a/src/PluginClientFactory.php b/src/PluginClientFactory.php index 6ae5091..3806e09 100644 --- a/src/PluginClientFactory.php +++ b/src/PluginClientFactory.php @@ -16,7 +16,7 @@ final class PluginClientFactory { /** - * @var callable + * @var callable|null */ private static $factory; From 60c1c7bf963fd22aa2879aabdc48abf2ca36c97d Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 17 Nov 2019 15:16:24 +0000 Subject: [PATCH 053/123] Added support for Symfony 5 and fixed incorrect version operators --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9d65c4a..be97911 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "php-http/httplug": "^2.0", "php-http/message-factory": "^1.0", "php-http/message": "^1.6", - "symfony/options-resolver": " ^3.4.20 || ^4.0.15 || ^4.1.9 || ^4.2.1" + "symfony/options-resolver": " ^3.4.20 || ~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0" }, "require-dev": { "doctrine/instantiator": "^1.1", From a8b29678d61556f45d6236b1667db16d998ceec5 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Mon, 18 Nov 2019 09:58:18 +0100 Subject: [PATCH 054/123] prepare release --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36b7bc5..b5f7625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 2.1.0 - 2019-11-18 + +### Added + +- Support Symfony 5 + ## 2.0.0 - 2019-02-03 ### Changed From c74cdf048fa8a8081fa830bfe8620f77a8f91df6 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 18 Nov 2019 10:03:40 +0100 Subject: [PATCH 055/123] bump branch alias --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index be97911..bc853de 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.1.x-dev" } } } From 1915f71dc4af9132074ce0ba88a967c86d912cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Sun, 6 Jan 2019 22:26:31 +0100 Subject: [PATCH 056/123] Add plugin client builder --- src/PluginClientBuilder.php | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/PluginClientBuilder.php diff --git a/src/PluginClientBuilder.php b/src/PluginClientBuilder.php new file mode 100644 index 0000000..c1012df --- /dev/null +++ b/src/PluginClientBuilder.php @@ -0,0 +1,71 @@ + + */ +final class PluginClientBuilder +{ + /** @var Plugin[][] List of plugins ordered by priority [priority => Plugin[]]). */ + private $plugins = []; + + /** @var array Array of options to give to the plugin client */ + private $options = []; + + /** + * @param int $priority Priority of the plugin. The higher comes first. + */ + public function addPlugin(Plugin $plugin, int $priority = 0): self + { + $this->plugins[$priority][] = $plugin; + + return $this; + } + + public function setOption(string $name, $value): self + { + $this->options[$name] = $value; + + return $this; + } + + public function removeOption(string $name): self + { + unset($this->options[$name]); + + return $this; + } + + /** + * @param ClientInterface | HttpAsyncClient $client + */ + public function createClient($client): PluginClient + { + if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) { + throw new \RuntimeException('You must provide a valid http client'); + } + + $plugins = $this->plugins; + + if (0 === count($plugins)) { + $plugins[] = []; + } + + krsort($plugins); + $plugins = array_merge(...$plugins); + + return new PluginClient( + $client, + array_values($plugins), + $this->options + ); + } +} From 0c0a37db65c702b6f03005878f567a8da0fbcaa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Sat, 23 Nov 2019 12:52:29 +0100 Subject: [PATCH 057/123] Add a test on the plugin client builder --- tests/PluginClientBuilderTest.php | 57 +++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/PluginClientBuilderTest.php diff --git a/tests/PluginClientBuilderTest.php b/tests/PluginClientBuilderTest.php new file mode 100644 index 0000000..422cf7e --- /dev/null +++ b/tests/PluginClientBuilderTest.php @@ -0,0 +1,57 @@ + $this->prophesize(Plugin::class)->reveal(), + -10 => $this->prophesize(Plugin::class)->reveal(), + 0 => $this->prophesize(Plugin::class)->reveal(), + ]; + + foreach ($plugins as $priority => $plugin) { + $builder->addPlugin($plugin, $priority); + } + + $client = $this->prophesize($client)->reveal(); + $client = $builder->createClient($client); + + $closure = Closure::bind( + function (): array { + return $this->plugins; + }, + $client, + PluginClient::class + ); + + $plugged = $closure(); + + $expected = $plugins; + krsort($expected); + $expected = array_values($expected); + + $this->assertSame($expected, $plugged); + } + + public function clientProvider(): iterable + { + yield 'sync\'d http client' => [HttpClient::class]; + yield 'async\'d http client' => [HttpAsyncClient::class]; + } +} From cbb22ef6b2004e5edc247514eb86b56059c19913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Sat, 23 Nov 2019 12:59:32 +0100 Subject: [PATCH 058/123] Add test on passing options from the client builder to the client --- tests/PluginClientBuilderTest.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/PluginClientBuilderTest.php b/tests/PluginClientBuilderTest.php index 422cf7e..190c4a5 100644 --- a/tests/PluginClientBuilderTest.php +++ b/tests/PluginClientBuilderTest.php @@ -49,6 +49,29 @@ function (): array { $this->assertSame($expected, $plugged); } + /** @dataProvider clientProvider */ + public function testOptions(string $client): void + { + $builder = new PluginClientBuilder(); + $builder->setOption('max_restarts', 5); + + $client = $this->prophesize($client)->reveal(); + $client = $builder->createClient($client); + + $closure = Closure::bind( + function (): array { + return $this->options; + }, + $client, + PluginClient::class + ); + + $options = $closure(); + + $this->assertArrayHasKey('max_restarts', $options); + $this->assertSame(5, $options['max_restarts']); + } + public function clientProvider(): iterable { yield 'sync\'d http client' => [HttpClient::class]; From 32300e2d860ce94799f5f1df697ac1cb0ceeeb7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Podlipsk=C3=BD?= Date: Thu, 12 Dec 2019 14:01:26 +0100 Subject: [PATCH 059/123] Update BaseUriPlugin.php Fix typo --- src/Plugin/BaseUriPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugin/BaseUriPlugin.php b/src/Plugin/BaseUriPlugin.php index 825709b..34c3b64 100644 --- a/src/Plugin/BaseUriPlugin.php +++ b/src/Plugin/BaseUriPlugin.php @@ -27,7 +27,7 @@ final class BaseUriPlugin implements Plugin private $addPathPlugin = null; /** - * @param UriInterface $uri Has to contain a host name and cans have a path + * @param UriInterface $uri Has to contain a host name and can have a path * @param array $hostConfig Config for AddHostPlugin. @see AddHostPlugin::configureOptions */ public function __construct(UriInterface $uri, array $hostConfig = []) From 0ad07cc1fcbc0447ac2cb20357089abc7318db4e Mon Sep 17 00:00:00 2001 From: Maxim Date: Sun, 15 Dec 2019 02:33:08 +0300 Subject: [PATCH 060/123] Add 7.4 for travis PHP 7.4 released, and we need run tests for 7.4 too --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9e10589..913e1aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ php: - 7.1 - 7.2 - 7.3 +- 7.4 matrix: fast_finish: true @@ -33,7 +34,7 @@ jobs: env: TEST_COMMAND="./vendor/bin/phpunit" DEPENDENCIES="phpunit/phpunit:^7.5 nyholm/psr7:^1.0" # Latest dev release - - php: 7.3 + - php: 7.4 env: STABILITY="dev" before_install: From 8c587e9c75afb64bf1237e9ac70f46f714fe7031 Mon Sep 17 00:00:00 2001 From: Maxim Date: Sun, 15 Dec 2019 19:55:51 +0300 Subject: [PATCH 061/123] Add phpspec v6 support --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index bc853de..2eb548d 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "require-dev": { "doctrine/instantiator": "^1.1", "guzzlehttp/psr7": "^1.4", - "phpspec/phpspec": "^5.1", + "phpspec/phpspec": "^5.1 || ^6.0", "phpspec/prophecy": "^1.8", "sebastian/comparator": "^3.0" }, From c805a4e570094c1cd8f9f1d91d31557b2b2ee472 Mon Sep 17 00:00:00 2001 From: Kai Date: Sun, 19 Jan 2020 10:46:30 +0100 Subject: [PATCH 062/123] adds phpunit as dev dependency, fixes existing tests, sets base php platform in composer to ensure only compatible dependencies --- composer.json | 18 ++++++++++++++++-- tests/Plugin/AddPathPluginTest.php | 21 ++++++++++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 2eb548d..74b1985 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,10 @@ "require-dev": { "doctrine/instantiator": "^1.1", "guzzlehttp/psr7": "^1.4", + "nyholm/psr7": "^1.2", "phpspec/phpspec": "^5.1 || ^6.0", "phpspec/prophecy": "^1.8", + "phpunit/phpunit": "^7.5", "sebastian/comparator": "^3.0" }, "suggest": { @@ -42,8 +44,20 @@ } }, "scripts": { - "test": "vendor/bin/phpspec run", - "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" + "test": [ + "vendor/bin/phpspec run", + "vendor/bin/phpunit" + ], + "test-ci": [ + "vendor/bin/phpspec run -c phpspec.ci.yml", + "vendor/bin/phpunit" + ] + }, + "config": { + "platform": { + "php": "7.1.33" + }, + "sort-packages": true }, "extra": { "branch-alias": { diff --git a/tests/Plugin/AddPathPluginTest.php b/tests/Plugin/AddPathPluginTest.php index 3980fa4..55081a8 100644 --- a/tests/Plugin/AddPathPluginTest.php +++ b/tests/Plugin/AddPathPluginTest.php @@ -4,8 +4,7 @@ use Http\Client\Common\Plugin; use Http\Client\Common\Plugin\AddPathPlugin; -use Http\Client\Common\PluginClient; -use Http\Client\HttpClient; +use Http\Client\Promise\HttpFulfilledPromise; use Nyholm\Psr7\Request; use Nyholm\Psr7\Response; use Nyholm\Psr7\Uri; @@ -24,7 +23,7 @@ class AddPathPluginTest extends TestCase */ private $first; - protected function setUp() + protected function setUp(): void { $this->first = function () {}; $this->plugin = new AddPathPlugin(new Uri('/api')); @@ -34,16 +33,18 @@ public function testRewriteSameUrl() { $verify = function (RequestInterface $request) { $this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString()); + + return new HttpFulfilledPromise(new Response()); }; - $request = new Request('GET', 'https://example.com/foo', ['Content-Type'=>'text/html']); + $request = new Request('GET', 'https://example.com/foo', ['Content-Type' => 'text/html']); $this->plugin->handleRequest($request, $verify, $this->first); // Make a second call with the same $request object $this->plugin->handleRequest($request, $verify, $this->first); // Make a new call with a new object but same URL - $request = new Request('GET', 'https://example.com/foo', ['Content-Type'=>'text/plain']); + $request = new Request('GET', 'https://example.com/foo', ['Content-Type' => 'text/plain']); $this->plugin->handleRequest($request, $verify, $this->first); } @@ -56,7 +57,11 @@ public function testRewriteCallingThePluginTwice() // Run the plugin again with the modified request $this->plugin->handleRequest($request, function (RequestInterface $request) { $this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString()); + + return new HttpFulfilledPromise(new Response()); }, $this->first); + + return new HttpFulfilledPromise(new Response()); }, $this->first); } @@ -65,11 +70,15 @@ public function testRewriteWithDifferentUrl() $request = new Request('GET', 'https://example.com/foo'); $this->plugin->handleRequest($request, function (RequestInterface $request) { $this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString()); + + return new HttpFulfilledPromise(new Response()); }, $this->first); $request = new Request('GET', 'https://example.com/bar'); $this->plugin->handleRequest($request, function (RequestInterface $request) { $this->assertEquals('https://example.com/api/bar', $request->getUri()->__toString()); + + return new HttpFulfilledPromise(new Response()); }, $this->first); } @@ -77,6 +86,8 @@ public function testRewriteWhenPathIsIncluded() { $verify = function (RequestInterface $request) { $this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString()); + + return new HttpFulfilledPromise(new Response()); }; $request = new Request('GET', 'https://example.com/api/foo'); From e7a93b833162152703178ea35e48a07b0a228572 Mon Sep 17 00:00:00 2001 From: Kai Date: Mon, 20 Jan 2020 12:35:18 +0100 Subject: [PATCH 063/123] removes setting for base php platform --- composer.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/composer.json b/composer.json index 74b1985..fb19fc3 100644 --- a/composer.json +++ b/composer.json @@ -54,9 +54,6 @@ ] }, "config": { - "platform": { - "php": "7.1.33" - }, "sort-packages": true }, "extra": { From e3e77f85f210523577f884c7ef734de85b3c9431 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 20 Mar 2020 10:56:52 +0100 Subject: [PATCH 064/123] allow symfony/options-resolver:^2.6 as dependency --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fb19fc3..6fae9c1 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "php-http/httplug": "^2.0", "php-http/message-factory": "^1.0", "php-http/message": "^1.6", - "symfony/options-resolver": " ^3.4.20 || ~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0" + "symfony/options-resolver": "^2.6 || ^3.4.20 || ~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0" }, "require-dev": { "doctrine/instantiator": "^1.1", From 7c52bf056f6319a4edaae6a9f5133e2129e9fa47 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 15 May 2020 11:09:32 +0200 Subject: [PATCH 065/123] do not overwrite jobs with the matrix --- .travis.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 913e1aa..8ad98bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,16 +5,8 @@ cache: directories: - $HOME/.composer/cache/files -php: -- 7.1 -- 7.2 -- 7.3 -- 7.4 - -matrix: - fast_finish: true - jobs: + fast_finish: true include: - php: 7.1 env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" From e7594555319b312a33871369621d843e9e7c09b0 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 1 Jul 2020 07:58:25 +0200 Subject: [PATCH 066/123] fix cs config --- .php_cs.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/.php_cs.dist b/.php_cs.dist index b241e67..3a0d4fc 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -19,5 +19,6 @@ return PhpCsFixer\Config::create() ], 'no_empty_phpdoc' => true, 'no_superfluous_phpdoc_tags' => true, + 'single_line_throw' => false, ]) ->setFinder($finder); From 546de53401ddcdccc79ce76723d0955ab07e2d8b Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 1 Jul 2020 08:05:19 +0200 Subject: [PATCH 067/123] cs: order imports --- spec/BatchClientSpec.php | 10 +++++----- spec/BatchResultSpec.php | 4 ++-- spec/EmulatedHttpAsyncClientSpec.php | 10 +++++----- spec/EmulatedHttpClientSpec.php | 6 +++--- spec/Exception/BatchExceptionSpec.php | 2 +- spec/FlexibleHttpClientSpec.php | 4 ++-- spec/HttpClientPool/HttpClientPoolItemSpec.php | 2 +- spec/HttpClientPool/LeastUsedClientPoolSpec.php | 6 +++--- spec/HttpClientPool/RandomClientPoolSpec.php | 6 +++--- spec/HttpClientPool/RoundRobinClientPoolSpec.php | 6 +++--- spec/HttpClientRouterSpec.php | 6 +++--- spec/Plugin/AddHostPluginSpec.php | 6 +++--- spec/Plugin/AddPathPluginSpec.php | 6 +++--- spec/Plugin/AuthenticationPluginSpec.php | 6 +++--- spec/Plugin/BaseUriPluginSpec.php | 6 +++--- spec/Plugin/ContentLengthPluginSpec.php | 8 ++++---- spec/Plugin/CookiePluginSpec.php | 10 +++++----- spec/Plugin/DecoderPluginSpec.php | 14 +++++++------- spec/Plugin/ErrorPluginSpec.php | 14 +++++++------- spec/Plugin/HistoryPluginSpec.php | 8 ++++---- spec/Plugin/QueryDefaultsPluginSpec.php | 4 ++-- spec/Plugin/RequestMatcherPluginSpec.php | 4 ++-- spec/Plugin/RetryPluginSpec.php | 8 ++++---- spec/PluginClientFactorySpec.php | 4 ++-- spec/PluginClientSpec.php | 8 ++++---- src/BatchResult.php | 2 -- src/Exception/BatchException.php | 2 +- src/HttpClientPool/HttpClientPoolItem.php | 2 +- 28 files changed, 86 insertions(+), 88 deletions(-) diff --git a/spec/BatchClientSpec.php b/spec/BatchClientSpec.php index 86c6c8b..7dcb261 100644 --- a/spec/BatchClientSpec.php +++ b/spec/BatchClientSpec.php @@ -2,14 +2,14 @@ namespace spec\Http\Client\Common; -use Http\Client\HttpClient; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; -use PhpSpec\ObjectBehavior; use Http\Client\Common\BatchClient; use Http\Client\Common\BatchResult; -use Http\Client\Exception\HttpException; use Http\Client\Common\Exception\BatchException; +use Http\Client\Exception\HttpException; +use Http\Client\HttpClient; +use PhpSpec\ObjectBehavior; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; class BatchClientSpec extends ObjectBehavior { diff --git a/spec/BatchResultSpec.php b/spec/BatchResultSpec.php index 90a80a5..775bb50 100644 --- a/spec/BatchResultSpec.php +++ b/spec/BatchResultSpec.php @@ -2,11 +2,11 @@ namespace spec\Http\Client\Common; +use Http\Client\Common\BatchResult; use Http\Client\Exception; +use PhpSpec\ObjectBehavior; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use PhpSpec\ObjectBehavior; -use Http\Client\Common\BatchResult; class BatchResultSpec extends ObjectBehavior { diff --git a/spec/EmulatedHttpAsyncClientSpec.php b/spec/EmulatedHttpAsyncClientSpec.php index 226008b..d5d114a 100644 --- a/spec/EmulatedHttpAsyncClientSpec.php +++ b/spec/EmulatedHttpAsyncClientSpec.php @@ -2,15 +2,15 @@ namespace spec\Http\Client\Common; -use Http\Client\HttpClient; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; -use PhpSpec\ObjectBehavior; use Http\Client\Common\EmulatedHttpAsyncClient; +use Http\Client\Exception\TransferException; use Http\Client\HttpAsyncClient; +use Http\Client\HttpClient; use Http\Client\Promise\HttpFulfilledPromise; -use Http\Client\Exception\TransferException; use Http\Client\Promise\HttpRejectedPromise; +use PhpSpec\ObjectBehavior; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; class EmulatedHttpAsyncClientSpec extends ObjectBehavior { diff --git a/spec/EmulatedHttpClientSpec.php b/spec/EmulatedHttpClientSpec.php index adf206f..cc9f91e 100644 --- a/spec/EmulatedHttpClientSpec.php +++ b/spec/EmulatedHttpClientSpec.php @@ -2,15 +2,15 @@ namespace spec\Http\Client\Common; +use Http\Client\Common\EmulatedHttpClient; +use Http\Client\Exception; use Http\Client\Exception\TransferException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Promise\Promise; +use PhpSpec\ObjectBehavior; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use PhpSpec\ObjectBehavior; -use Http\Client\Common\EmulatedHttpClient; -use Http\Client\Exception; class EmulatedHttpClientSpec extends ObjectBehavior { diff --git a/spec/Exception/BatchExceptionSpec.php b/spec/Exception/BatchExceptionSpec.php index 0205843..be72814 100644 --- a/spec/Exception/BatchExceptionSpec.php +++ b/spec/Exception/BatchExceptionSpec.php @@ -3,9 +3,9 @@ namespace spec\Http\Client\Common\Exception; use Http\Client\Common\BatchResult; +use Http\Client\Common\Exception\BatchException; use Http\Client\Exception; use PhpSpec\ObjectBehavior; -use Http\Client\Common\Exception\BatchException; class BatchExceptionSpec extends ObjectBehavior { diff --git a/spec/FlexibleHttpClientSpec.php b/spec/FlexibleHttpClientSpec.php index b63aa3d..85de240 100644 --- a/spec/FlexibleHttpClientSpec.php +++ b/spec/FlexibleHttpClientSpec.php @@ -2,13 +2,13 @@ namespace spec\Http\Client\Common; +use Http\Client\Common\FlexibleHttpClient; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Promise\Promise; +use PhpSpec\ObjectBehavior; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use PhpSpec\ObjectBehavior; -use Http\Client\Common\FlexibleHttpClient; class FlexibleHttpClientSpec extends ObjectBehavior { diff --git a/spec/HttpClientPool/HttpClientPoolItemSpec.php b/spec/HttpClientPool/HttpClientPoolItemSpec.php index 22c0b7d..537378f 100644 --- a/spec/HttpClientPool/HttpClientPoolItemSpec.php +++ b/spec/HttpClientPool/HttpClientPoolItemSpec.php @@ -3,6 +3,7 @@ namespace spec\Http\Client\Common\HttpClientPool; use Http\Client\Exception; +use Http\Client\Exception\RequestException; use Http\Client\Exception\TransferException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; @@ -12,7 +13,6 @@ use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Http\Client\Exception\RequestException; class HttpClientPoolItemSpec extends ObjectBehavior { diff --git a/spec/HttpClientPool/LeastUsedClientPoolSpec.php b/spec/HttpClientPool/LeastUsedClientPoolSpec.php index 524af9b..367288d 100644 --- a/spec/HttpClientPool/LeastUsedClientPoolSpec.php +++ b/spec/HttpClientPool/LeastUsedClientPoolSpec.php @@ -2,7 +2,10 @@ namespace spec\Http\Client\Common\HttpClientPool; +use Http\Client\Common\Exception\HttpClientNotFoundException; use Http\Client\Common\HttpClientPool\HttpClientPoolItem; +use Http\Client\Common\HttpClientPool\LeastUsedClientPool; +use Http\Client\Exception\HttpException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Promise\Promise; @@ -10,9 +13,6 @@ use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Http\Client\Common\HttpClientPool\LeastUsedClientPool; -use Http\Client\Common\Exception\HttpClientNotFoundException; -use Http\Client\Exception\HttpException; class LeastUsedClientPoolSpec extends ObjectBehavior { diff --git a/spec/HttpClientPool/RandomClientPoolSpec.php b/spec/HttpClientPool/RandomClientPoolSpec.php index 317bcd8..5cb34c3 100644 --- a/spec/HttpClientPool/RandomClientPoolSpec.php +++ b/spec/HttpClientPool/RandomClientPoolSpec.php @@ -2,7 +2,10 @@ namespace spec\Http\Client\Common\HttpClientPool; +use Http\Client\Common\Exception\HttpClientNotFoundException; use Http\Client\Common\HttpClientPool\HttpClientPoolItem; +use Http\Client\Common\HttpClientPool\RandomClientPool; +use Http\Client\Exception\HttpException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Promise\Promise; @@ -10,9 +13,6 @@ use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Http\Client\Common\HttpClientPool\RandomClientPool; -use Http\Client\Common\Exception\HttpClientNotFoundException; -use Http\Client\Exception\HttpException; class RandomClientPoolSpec extends ObjectBehavior { diff --git a/spec/HttpClientPool/RoundRobinClientPoolSpec.php b/spec/HttpClientPool/RoundRobinClientPoolSpec.php index 1b4a243..5a272b2 100644 --- a/spec/HttpClientPool/RoundRobinClientPoolSpec.php +++ b/spec/HttpClientPool/RoundRobinClientPoolSpec.php @@ -2,7 +2,10 @@ namespace spec\Http\Client\Common\HttpClientPool; +use Http\Client\Common\Exception\HttpClientNotFoundException; use Http\Client\Common\HttpClientPool\HttpClientPoolItem; +use Http\Client\Common\HttpClientPool\RoundRobinClientPool; +use Http\Client\Exception\HttpException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Promise\Promise; @@ -10,9 +13,6 @@ use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Http\Client\Common\HttpClientPool\RoundRobinClientPool; -use Http\Client\Common\Exception\HttpClientNotFoundException; -use Http\Client\Exception\HttpException; class RoundRobinClientPoolSpec extends ObjectBehavior { diff --git a/spec/HttpClientRouterSpec.php b/spec/HttpClientRouterSpec.php index f90c034..db2f112 100644 --- a/spec/HttpClientRouterSpec.php +++ b/spec/HttpClientRouterSpec.php @@ -4,14 +4,14 @@ use Http\Client\Common\Exception\HttpClientNoMatchException; use Http\Client\Common\HttpClientRouter; -use Http\Message\RequestMatcher; +use Http\Client\Common\HttpClientRouterInterface; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; +use Http\Message\RequestMatcher; use Http\Promise\Promise; +use PhpSpec\ObjectBehavior; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use PhpSpec\ObjectBehavior; -use Http\Client\Common\HttpClientRouterInterface; class HttpClientRouterSpec extends ObjectBehavior { diff --git a/spec/Plugin/AddHostPluginSpec.php b/spec/Plugin/AddHostPluginSpec.php index 2691f10..e90ddfc 100644 --- a/spec/Plugin/AddHostPluginSpec.php +++ b/spec/Plugin/AddHostPluginSpec.php @@ -2,11 +2,11 @@ namespace spec\Http\Client\Common\Plugin; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\AddHostPlugin; +use PhpSpec\ObjectBehavior; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; -use PhpSpec\ObjectBehavior; -use Http\Client\Common\Plugin\AddHostPlugin; -use Http\Client\Common\Plugin; class AddHostPluginSpec extends ObjectBehavior { diff --git a/spec/Plugin/AddPathPluginSpec.php b/spec/Plugin/AddPathPluginSpec.php index 1c6f09d..4075622 100644 --- a/spec/Plugin/AddPathPluginSpec.php +++ b/spec/Plugin/AddPathPluginSpec.php @@ -2,11 +2,11 @@ namespace spec\Http\Client\Common\Plugin; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\AddPathPlugin; +use PhpSpec\ObjectBehavior; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; -use PhpSpec\ObjectBehavior; -use Http\Client\Common\Plugin\AddPathPlugin; -use Http\Client\Common\Plugin; class AddPathPluginSpec extends ObjectBehavior { diff --git a/spec/Plugin/AuthenticationPluginSpec.php b/spec/Plugin/AuthenticationPluginSpec.php index c86cb3a..3191ade 100644 --- a/spec/Plugin/AuthenticationPluginSpec.php +++ b/spec/Plugin/AuthenticationPluginSpec.php @@ -2,13 +2,13 @@ namespace spec\Http\Client\Common\Plugin; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\AuthenticationPlugin; use Http\Message\Authentication; use Http\Promise\Promise; -use Psr\Http\Message\RequestInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; -use Http\Client\Common\Plugin\AuthenticationPlugin; -use Http\Client\Common\Plugin; +use Psr\Http\Message\RequestInterface; class AuthenticationPluginSpec extends ObjectBehavior { diff --git a/spec/Plugin/BaseUriPluginSpec.php b/spec/Plugin/BaseUriPluginSpec.php index 92f5074..41d876b 100644 --- a/spec/Plugin/BaseUriPluginSpec.php +++ b/spec/Plugin/BaseUriPluginSpec.php @@ -2,11 +2,11 @@ namespace spec\Http\Client\Common\Plugin; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\BaseUriPlugin; +use PhpSpec\ObjectBehavior; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; -use PhpSpec\ObjectBehavior; -use Http\Client\Common\Plugin\BaseUriPlugin; -use Http\Client\Common\Plugin; class BaseUriPluginSpec extends ObjectBehavior { diff --git a/spec/Plugin/ContentLengthPluginSpec.php b/spec/Plugin/ContentLengthPluginSpec.php index a945924..39a8b71 100644 --- a/spec/Plugin/ContentLengthPluginSpec.php +++ b/spec/Plugin/ContentLengthPluginSpec.php @@ -2,13 +2,13 @@ namespace spec\Http\Client\Common\Plugin; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\ContentLengthPlugin; use PhpSpec\Exception\Example\SkippingException; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; -use Http\Client\Common\Plugin\ContentLengthPlugin; -use Http\Client\Common\Plugin; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\StreamInterface; class ContentLengthPluginSpec extends ObjectBehavior { diff --git a/spec/Plugin/CookiePluginSpec.php b/spec/Plugin/CookiePluginSpec.php index 7bb188c..be5cd62 100644 --- a/spec/Plugin/CookiePluginSpec.php +++ b/spec/Plugin/CookiePluginSpec.php @@ -2,18 +2,18 @@ namespace spec\Http\Client\Common\Plugin; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\CookiePlugin; +use Http\Client\Exception\TransferException; use Http\Client\Promise\HttpFulfilledPromise; +use Http\Client\Promise\HttpRejectedPromise; use Http\Message\Cookie; use Http\Message\CookieJar; use Http\Promise\Promise; +use PhpSpec\ObjectBehavior; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; -use PhpSpec\ObjectBehavior; -use Http\Client\Common\Plugin\CookiePlugin; -use Http\Client\Common\Plugin; -use Http\Client\Promise\HttpRejectedPromise; -use Http\Client\Exception\TransferException; class CookiePluginSpec extends ObjectBehavior { diff --git a/spec/Plugin/DecoderPluginSpec.php b/spec/Plugin/DecoderPluginSpec.php index 1316a90..c3731c8 100644 --- a/spec/Plugin/DecoderPluginSpec.php +++ b/spec/Plugin/DecoderPluginSpec.php @@ -2,17 +2,17 @@ namespace spec\Http\Client\Common\Plugin; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\DecoderPlugin; use Http\Client\Promise\HttpFulfilledPromise; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamInterface; +use Http\Message\Encoding\DecompressStream; +use Http\Message\Encoding\GzipDecodeStream; use PhpSpec\Exception\Example\SkippingException; use PhpSpec\ObjectBehavior; use Prophecy\Argument; -use Http\Client\Common\Plugin\DecoderPlugin; -use Http\Client\Common\Plugin; -use Http\Message\Encoding\GzipDecodeStream; -use Http\Message\Encoding\DecompressStream; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; class DecoderPluginSpec extends ObjectBehavior { diff --git a/spec/Plugin/ErrorPluginSpec.php b/spec/Plugin/ErrorPluginSpec.php index 205192a..7861246 100644 --- a/spec/Plugin/ErrorPluginSpec.php +++ b/spec/Plugin/ErrorPluginSpec.php @@ -2,16 +2,16 @@ namespace spec\Http\Client\Common\Plugin; +use Http\Client\Common\Exception\ClientErrorException; +use Http\Client\Common\Exception\ServerErrorException; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\ErrorPlugin; use Http\Client\Promise\HttpFulfilledPromise; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; +use Http\Client\Promise\HttpRejectedPromise; use PhpSpec\ObjectBehavior; use Prophecy\Argument; -use Http\Client\Common\Plugin\ErrorPlugin; -use Http\Client\Common\Plugin; -use Http\Client\Promise\HttpRejectedPromise; -use Http\Client\Common\Exception\ClientErrorException; -use Http\Client\Common\Exception\ServerErrorException; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; class ErrorPluginSpec extends ObjectBehavior { diff --git a/spec/Plugin/HistoryPluginSpec.php b/spec/Plugin/HistoryPluginSpec.php index 495e5d5..cd8459b 100644 --- a/spec/Plugin/HistoryPluginSpec.php +++ b/spec/Plugin/HistoryPluginSpec.php @@ -2,15 +2,15 @@ namespace spec\Http\Client\Common\Plugin; -use Http\Client\Exception\TransferException; +use Http\Client\Common\Plugin; use Http\Client\Common\Plugin\Journal; +use Http\Client\Exception\TransferException; use Http\Client\Promise\HttpFulfilledPromise; use Http\Client\Promise\HttpRejectedPromise; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; -use Http\Client\Common\Plugin; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; class HistoryPluginSpec extends ObjectBehavior { diff --git a/spec/Plugin/QueryDefaultsPluginSpec.php b/spec/Plugin/QueryDefaultsPluginSpec.php index 8077261..343065a 100644 --- a/spec/Plugin/QueryDefaultsPluginSpec.php +++ b/spec/Plugin/QueryDefaultsPluginSpec.php @@ -3,10 +3,10 @@ namespace spec\Http\Client\Common\Plugin; use Http\Client\Common\Plugin; -use Psr\Http\Message\RequestInterface; +use Http\Client\Common\Plugin\QueryDefaultsPlugin; use PhpSpec\ObjectBehavior; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; -use Http\Client\Common\Plugin\QueryDefaultsPlugin; class QueryDefaultsPluginSpec extends ObjectBehavior { diff --git a/spec/Plugin/RequestMatcherPluginSpec.php b/spec/Plugin/RequestMatcherPluginSpec.php index de7cae6..246dc29 100644 --- a/spec/Plugin/RequestMatcherPluginSpec.php +++ b/spec/Plugin/RequestMatcherPluginSpec.php @@ -3,12 +3,12 @@ namespace spec\Http\Client\Common\Plugin; use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\RequestMatcherPlugin; use Http\Message\RequestMatcher; use Http\Promise\Promise; -use Psr\Http\Message\RequestInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; -use Http\Client\Common\Plugin\RequestMatcherPlugin; +use Psr\Http\Message\RequestInterface; class RequestMatcherPluginSpec extends ObjectBehavior { diff --git a/spec/Plugin/RetryPluginSpec.php b/spec/Plugin/RetryPluginSpec.php index 41bf059..4b749a7 100644 --- a/spec/Plugin/RetryPluginSpec.php +++ b/spec/Plugin/RetryPluginSpec.php @@ -2,15 +2,15 @@ namespace spec\Http\Client\Common\Plugin; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\RetryPlugin; use Http\Client\Exception; use Http\Client\Promise\HttpFulfilledPromise; use Http\Client\Promise\HttpRejectedPromise; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; -use Http\Client\Common\Plugin\RetryPlugin; -use Http\Client\Common\Plugin; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; class RetryPluginSpec extends ObjectBehavior { diff --git a/spec/PluginClientFactorySpec.php b/spec/PluginClientFactorySpec.php index d7a3acb..6b5868d 100644 --- a/spec/PluginClientFactorySpec.php +++ b/spec/PluginClientFactorySpec.php @@ -2,10 +2,10 @@ namespace spec\Http\Client\Common; +use Http\Client\Common\PluginClient; +use Http\Client\Common\PluginClientFactory; use Http\Client\HttpClient; use PhpSpec\ObjectBehavior; -use Http\Client\Common\PluginClientFactory; -use Http\Client\Common\PluginClient; class PluginClientFactorySpec extends ObjectBehavior { diff --git a/spec/PluginClientSpec.php b/spec/PluginClientSpec.php index c5c2932..fb6b153 100644 --- a/spec/PluginClientSpec.php +++ b/spec/PluginClientSpec.php @@ -2,16 +2,16 @@ namespace spec\Http\Client\Common; +use Http\Client\Common\Exception\LoopException; +use Http\Client\Common\Plugin; +use Http\Client\Common\PluginClient; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; -use Http\Client\Common\Plugin; use Http\Promise\Promise; +use PhpSpec\ObjectBehavior; use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use PhpSpec\ObjectBehavior; -use Http\Client\Common\Exception\LoopException; -use Http\Client\Common\PluginClient; class PluginClientSpec extends ObjectBehavior { diff --git a/src/BatchResult.php b/src/BatchResult.php index f353833..45e1535 100644 --- a/src/BatchResult.php +++ b/src/BatchResult.php @@ -66,7 +66,6 @@ public function isSuccessful(RequestInterface $request): bool /** * Returns the response for a successful request. * - * * @throws \UnexpectedValueException If request was not part of the batch or failed */ public function getResponseFor(RequestInterface $request): ResponseInterface @@ -126,7 +125,6 @@ public function isFailed(RequestInterface $request): bool /** * Returns the exception for a failed request. * - * * @throws \UnexpectedValueException If request was not part of the batch or was successful */ public function getExceptionFor(RequestInterface $request): ClientExceptionInterface diff --git a/src/Exception/BatchException.php b/src/Exception/BatchException.php index cbda665..a9cb08c 100644 --- a/src/Exception/BatchException.php +++ b/src/Exception/BatchException.php @@ -4,8 +4,8 @@ namespace Http\Client\Common\Exception; -use Http\Client\Exception\TransferException; use Http\Client\Common\BatchResult; +use Http\Client\Exception\TransferException; /** * This exception is thrown when HttpClient::sendRequests led to at least one failure. diff --git a/src/HttpClientPool/HttpClientPoolItem.php b/src/HttpClientPool/HttpClientPoolItem.php index 2c000ba..c22c080 100644 --- a/src/HttpClientPool/HttpClientPoolItem.php +++ b/src/HttpClientPool/HttpClientPoolItem.php @@ -5,11 +5,11 @@ namespace Http\Client\Common\HttpClientPool; use Http\Client\Common\FlexibleHttpClient; +use Http\Client\Exception; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; -use Http\Client\Exception; use Psr\Http\Message\ResponseInterface; /** From b41b922eeeff3331762e304eeea643a55bc1fd0b Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 1 Jul 2020 10:44:12 +0100 Subject: [PATCH 068/123] Support the PSR17 request factory --- composer.json | 5 ++++- src/HttpMethodsClient.php | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 6fae9c1..59b3e51 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,10 @@ "php-http/httplug": "^2.0", "php-http/message-factory": "^1.0", "php-http/message": "^1.6", - "symfony/options-resolver": "^2.6 || ^3.4.20 || ~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0" + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "symfony/options-resolver": "^2.6 || ^3.4.20 || ~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0", + "symfony/polyfill-php80": "^1.17" }, "require-dev": { "doctrine/instantiator": "^1.1", diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index 5b49c7c..bbe95be 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -6,6 +6,7 @@ use Http\Message\RequestFactory; use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -17,12 +18,21 @@ final class HttpMethodsClient implements HttpMethodsClientInterface private $httpClient; /** - * @var RequestFactory + * @var RequestFactory|RequestFactoryInterface */ private $requestFactory; - public function __construct(ClientInterface $httpClient, RequestFactory $requestFactory) + /** + * @param RequestFactory|RequestFactoryInterface + */ + public function __construct(ClientInterface $httpClient, $requestFactory) { + if (!$requestFactory instanceof RequestFactory && !$requestFactory instanceof RequestFactoryInterface) { + throw new \TypeError( + sprintf('%s::__construct(): Argument #2 ($requestFactory) must be of type %s|%s, %s given', self::class, RequestFactory::class, RequestFactoryInterface::class, get_debug_type($requestFactory)) + ); + } + $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; } From 86b2060141f91fe7b6ba8515c16cbc00f416d87d Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 1 Jul 2020 14:26:29 +0100 Subject: [PATCH 069/123] Make the next release 2.2.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 59b3e51..57270ef 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "2.2.x-dev" } } } From 1e537394ee59255f554577c8d7ddbb415525a5ec Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 1 Jul 2020 14:41:12 +0100 Subject: [PATCH 070/123] Consitent implementation of union type checking --- src/FlexibleHttpClient.php | 6 ++++-- src/HttpClientPool/HttpClientPool.php | 6 ++++++ src/HttpClientPool/HttpClientPoolItem.php | 6 ++++++ src/HttpClientRouter.php | 6 ++++++ src/PluginClient.php | 6 +++--- src/PluginClientBuilder.php | 6 ++++-- 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/FlexibleHttpClient.php b/src/FlexibleHttpClient.php index d6f1862..1a602c4 100644 --- a/src/FlexibleHttpClient.php +++ b/src/FlexibleHttpClient.php @@ -24,8 +24,10 @@ final class FlexibleHttpClient implements HttpClient, HttpAsyncClient */ public function __construct($client) { - if (!($client instanceof ClientInterface) && !($client instanceof HttpAsyncClient)) { - throw new \LogicException(sprintf('Client must be an instance of %s or %s', ClientInterface::class, HttpAsyncClient::class)); + if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) { + throw new \TypeError( + sprintf('%s::__construct(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client)) + ); } $this->httpClient = $client; diff --git a/src/HttpClientPool/HttpClientPool.php b/src/HttpClientPool/HttpClientPool.php index ffa21d2..0665aa7 100644 --- a/src/HttpClientPool/HttpClientPool.php +++ b/src/HttpClientPool/HttpClientPool.php @@ -29,6 +29,12 @@ abstract class HttpClientPool implements HttpClientPoolInterface */ public function addHttpClient($client): void { + if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient && !$client instanceof HttpClientPoolItem) { + throw new \TypeError( + sprintf('%s::addHttpClient(): Argument #1 ($client) must be of type %s|%s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, HttpClientPoolItem::class, get_debug_type($client)) + ); + } + if (!$client instanceof HttpClientPoolItem) { $client = new HttpClientPoolItem($client); } diff --git a/src/HttpClientPool/HttpClientPoolItem.php b/src/HttpClientPool/HttpClientPoolItem.php index c22c080..f29d065 100644 --- a/src/HttpClientPool/HttpClientPoolItem.php +++ b/src/HttpClientPool/HttpClientPoolItem.php @@ -59,6 +59,12 @@ class HttpClientPoolItem implements HttpClient, HttpAsyncClient */ public function __construct($client, int $reenableAfter = null) { + if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) { + throw new \TypeError( + sprintf('%s::__construct(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client)) + ); + } + $this->client = new FlexibleHttpClient($client); $this->reenableAfter = $reenableAfter; } diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index 84c7e04..78a4e69 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -46,6 +46,12 @@ public function sendAsyncRequest(RequestInterface $request) */ public function addClient($client, RequestMatcher $requestMatcher): void { + if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) { + throw new \TypeError( + sprintf('%s::addClient(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client)) + ); + } + $this->clients[] = [ 'matcher' => $requestMatcher, 'client' => new FlexibleHttpClient($client), diff --git a/src/PluginClient.php b/src/PluginClient.php index 7a44aab..f935b0d 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -50,8 +50,6 @@ final class PluginClient implements HttpClient, HttpAsyncClient * * @var int $max_restarts * } - * - * @throws \RuntimeException if client is not an instance of HttpClient or HttpAsyncClient */ public function __construct($client, array $plugins = [], array $options = []) { @@ -60,7 +58,9 @@ public function __construct($client, array $plugins = [], array $options = []) } elseif ($client instanceof ClientInterface) { $this->client = new EmulatedHttpAsyncClient($client); } else { - throw new \LogicException(sprintf('Client must be an instance of %s or %s', ClientInterface::class, HttpAsyncClient::class)); + throw new \TypeError( + sprintf('%s::__construct(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client)) + ); } $this->plugins = $plugins; diff --git a/src/PluginClientBuilder.php b/src/PluginClientBuilder.php index c1012df..87ee280 100644 --- a/src/PluginClientBuilder.php +++ b/src/PluginClientBuilder.php @@ -45,12 +45,14 @@ public function removeOption(string $name): self } /** - * @param ClientInterface | HttpAsyncClient $client + * @param ClientInterface|HttpAsyncClient $client */ public function createClient($client): PluginClient { if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) { - throw new \RuntimeException('You must provide a valid http client'); + throw new \TypeError( + sprintf('%s::createClient(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client)) + ); } $plugins = $this->plugins; From ec6e78a396d3fee6225095aa49b9bbfa83941561 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 1 Jul 2020 14:50:06 +0100 Subject: [PATCH 071/123] Fixes --- spec/FlexibleHttpClientSpec.php | 4 ++-- src/PluginClientFactory.php | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/FlexibleHttpClientSpec.php b/spec/FlexibleHttpClientSpec.php index 85de240..ec88da7 100644 --- a/spec/FlexibleHttpClientSpec.php +++ b/spec/FlexibleHttpClientSpec.php @@ -32,11 +32,11 @@ public function it_is_an_async_http_client() $this->shouldImplement(HttpAsyncClient::class); } - public function it_throw_exception_if_invalid_client() + public function it_throw_type_error_if_invalid_client() { $this->beConstructedWith(null); - $this->shouldThrow(\LogicException::class)->duringInstantiation(); + $this->shouldThrow(\TypeError::class)->duringInstantiation(); } public function it_emulates_an_async_client( diff --git a/src/PluginClientFactory.php b/src/PluginClientFactory.php index 3806e09..bbdc5c3 100644 --- a/src/PluginClientFactory.php +++ b/src/PluginClientFactory.php @@ -47,6 +47,12 @@ public static function setFactory(callable $factory) */ public function createClient($client, array $plugins = [], array $options = []): PluginClient { + if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) { + throw new \TypeError( + sprintf('%s::createClient(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client)) + ); + } + if (static::$factory) { $factory = static::$factory; From ed109d6749312f1d92a939adcd7e093239b5c292 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 1 Jul 2020 15:29:18 +0100 Subject: [PATCH 072/123] Replacement upgrade phpspec-code-coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Dan Michael O. Heggø --- .travis.yml | 2 +- phpspec.ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8ad98bc..436011a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ jobs: - php: 7.1 env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" - php: 7.2 - env: COVERAGE=true DEPENDENCIES="leanphp/phpspec-code-coverage phpspec/phpspec:^4.2" + env: COVERAGE=true DEPENDENCIES="friends-of-phpspec/phpspec-code-coverage:^4.3 phpspec/phpspec:^5.1" script: - composer test-ci after_success: diff --git a/phpspec.ci.yml b/phpspec.ci.yml index d8e1383..06a7469 100644 --- a/phpspec.ci.yml +++ b/phpspec.ci.yml @@ -4,7 +4,7 @@ suites: psr4_prefix: Http\Client\Common formatter.name: pretty extensions: - LeanPHP\PhpSpec\CodeCoverage\CodeCoverageExtension: ~ + FriendsOfPhpSpec\PhpSpec\CodeCoverage\CodeCoverageExtension: ~ code_coverage: format: clover output: build/coverage.xml From 6aaedfabab88c9490b26eee79463b28fe78a556f Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 1 Jul 2020 15:32:37 +0100 Subject: [PATCH 073/123] memory leak fix (#194) Fix memory leak recreating new chain instances --- src/PluginChain.php | 65 +++++++++++++++++++++++++++ src/PluginClient.php | 34 +++----------- tests/PluginChainTest.php | 93 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 28 deletions(-) create mode 100644 src/PluginChain.php create mode 100644 tests/PluginChainTest.php diff --git a/src/PluginChain.php b/src/PluginChain.php new file mode 100644 index 0000000..220ba36 --- /dev/null +++ b/src/PluginChain.php @@ -0,0 +1,65 @@ +plugins = $plugins; + $this->clientCallable = $clientCallable; + $this->maxRestarts = (int) ($options['max_restarts'] ?? 0); + } + + private function createChain(): callable + { + $lastCallable = $this->clientCallable; + $reversedPlugins = array_reverse($this->plugins); + + foreach ($reversedPlugins as $plugin) { + $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable) { + return $plugin->handleRequest($request, $lastCallable, $this); + }; + } + + return $lastCallable; + } + + public function __invoke(RequestInterface $request): Promise + { + if ($this->restarts > $this->maxRestarts) { + throw new LoopException('Too many restarts in plugin client', $request); + } + + ++$this->restarts; + + return $this->createChain()($request); + } +} diff --git a/src/PluginClient.php b/src/PluginClient.php index f935b0d..0d330b1 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -4,7 +4,6 @@ namespace Http\Client\Common; -use Http\Client\Common\Exception\LoopException; use Http\Client\Exception as HttplugException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; @@ -44,11 +43,11 @@ final class PluginClient implements HttpClient, HttpAsyncClient private $options; /** - * @param ClientInterface|HttpAsyncClient $client - * @param Plugin[] $plugins + * @param ClientInterface|HttpAsyncClient $client An HTTP async client + * @param Plugin[] $plugins A plugin chain * @param array $options { * - * @var int $max_restarts + * @var int $max_restarts * } */ public function __construct($client, array $plugins = [], array $options = []) @@ -120,32 +119,11 @@ private function configure(array $options = []): array /** * Create the plugin chain. * - * @param Plugin[] $pluginList A list of plugins + * @param Plugin[] $plugins A plugin chain * @param callable $clientCallable Callable making the HTTP call */ - private function createPluginChain(array $pluginList, callable $clientCallable): callable + private function createPluginChain(array $plugins, callable $clientCallable): callable { - $firstCallable = $lastCallable = $clientCallable; - - while ($plugin = array_pop($pluginList)) { - $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) { - return $plugin->handleRequest($request, $lastCallable, $firstCallable); - }; - - $firstCallable = $lastCallable; - } - - $firstCalls = 0; - $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) { - if ($firstCalls > $this->options['max_restarts']) { - throw new LoopException('Too many restarts in plugin client', $request); - } - - ++$firstCalls; - - return $lastCallable($request); - }; - - return $firstCallable; + return new PluginChain($plugins, $clientCallable, $this->options); } } diff --git a/tests/PluginChainTest.php b/tests/PluginChainTest.php new file mode 100644 index 0000000..95fdfab --- /dev/null +++ b/tests/PluginChainTest.php @@ -0,0 +1,93 @@ +func = $func; + } + + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise + { + ($this->func)($request, $next, $first); + + return $next($request); + } + }; + } + + public function testChainShouldInvokePluginsInReversedOrder(): void + { + $pluginOrderCalls = []; + + $plugin1 = $this->createPlugin(static function () use (&$pluginOrderCalls) { + $pluginOrderCalls[] = 'plugin1'; + }); + $plugin2 = $this->createPlugin(static function () use (&$pluginOrderCalls) { + $pluginOrderCalls[] = 'plugin2'; + }); + + $request = $this->prophesize(RequestInterface::class); + $responsePromise = $this->prophesize(Promise::class); + + $clientCallable = static function () use ($responsePromise) { + return $responsePromise->reveal(); + }; + + $pluginOrderCalls = []; + + $plugins = [ + $plugin1, + $plugin2, + ]; + + $pluginChain = new PluginChain($plugins, $clientCallable); + + $result = $pluginChain($request->reveal()); + + $this->assertSame($responsePromise->reveal(), $result); + $this->assertSame(['plugin1', 'plugin2'], $pluginOrderCalls); + } + + public function testShouldThrowLoopExceptionOnMaxRestarts(): void + { + $this->expectException(LoopException::class); + + $request = $this->prophesize(RequestInterface::class); + $responsePromise = $this->prophesize(Promise::class); + $calls = 0; + $clientCallable = static function () use ($responsePromise, &$calls) { + ++$calls; + + return $responsePromise->reveal(); + }; + + $pluginChain = new PluginChain([], $clientCallable, ['max_restarts' => 2]); + + $pluginChain($request->reveal()); + $this->assertSame(1, $calls); + $pluginChain($request->reveal()); + $this->assertSame(2, $calls); + $pluginChain($request->reveal()); + $this->assertSame(3, $calls); + $pluginChain($request->reveal()); + } +} From 0b4eb02fc63eb2396f15be231af3f9b74992d659 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 1 Jul 2020 15:40:30 +0100 Subject: [PATCH 074/123] Added change log entries for the next release --- CHANGELOG.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5f7625..55fb98a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Change Log +## UPCOMING + +### Added + +- Plugin client builder for making a `PluginClient` +- Support for the PSR17 request factory in `HttpMethodsClient` + +### Changed + +- Restored support for `symfony/options-resolver: ^2.6` +- Consitent implementation of union type checking + +### Fixed + +- Memory leak when using the `PluginClient` with plugins + ## 2.1.0 - 2019-11-18 ### Added @@ -9,6 +25,7 @@ ## 2.0.0 - 2019-02-03 ### Changed + - HttpClientRouter now throws a HttpClientNoMatchException instead of a RequestException if it can not find a client for the request. - RetryPlugin will only retry exceptions when there is no response, or a response in the 5xx HTTP code range. - RetryPlugin also retries when no exception is thrown if the responses has HTTP code in the 5xx range. @@ -23,6 +40,7 @@ - All exceptions in `Http\Client\Common\Exception` are final. ### Removed + - Deprecated option `debug_plugins` has been removed from `PluginClient` - Deprecated options `decider` and `delay` have been removed from `RetryPlugin`, use `exception_decider` and `exception_delay` instead. @@ -73,7 +91,6 @@ - Decoder plugin will now remove header when there is no more encoding, instead of setting to an empty array - ## 1.7.0 - 2017-11-30 ### Added From 42287e3b6cffdeab621a0696732a25b39afc18e7 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 1 Jul 2020 16:07:04 +0100 Subject: [PATCH 075/123] Added explicit psr/http-client dependency --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 57270ef..5f8e4bb 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "php-http/httplug": "^2.0", "php-http/message-factory": "^1.0", "php-http/message": "^1.6", + "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.0", "symfony/options-resolver": "^2.6 || ^3.4.20 || ~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0", From 23d6d7a4a75aca1d226ad82e7302e8ceff31822c Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 1 Jul 2020 16:08:16 +0100 Subject: [PATCH 076/123] Go with PSR-17 Co-authored-by: David Buchmann --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55fb98a..bf9d66f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Added - Plugin client builder for making a `PluginClient` -- Support for the PSR17 request factory in `HttpMethodsClient` +- Support for the PSR-17 request factory in `HttpMethodsClient` ### Changed From 5b908c8449d5da6e62ff77f32d5ae4100743f576 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 1 Jul 2020 16:08:39 +0100 Subject: [PATCH 077/123] Release 2.2.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf9d66f..0dfa38a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## UPCOMING +## 2.2.0 - 2020-07-02 ### Added From 508ab5d8560a4675a64486ef35c151a80f03bb35 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 2 Jul 2020 08:50:16 +0200 Subject: [PATCH 078/123] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dfa38a..f612419 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### Changed - Restored support for `symfony/options-resolver: ^2.6` -- Consitent implementation of union type checking +- Consistent implementation of union type checking ### Fixed From 0f661249e3b5f103c085ca706afbe359a75b0111 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 6 Jul 2020 19:06:32 +0100 Subject: [PATCH 079/123] More specific plugin type doc --- src/Plugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Plugin.php b/src/Plugin.php index cbc9592..99898b9 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -24,8 +24,8 @@ interface Plugin * * @see http://docs.php-http.org/en/latest/plugins/build-your-own.html * - * @param callable $next Next middleware in the chain, the request is passed as the first argument - * @param callable $first First middleware in the chain, used to to restart a request + * @param callable(RequestInterface): Promise $next Next middleware in the chain, the request is passed as the first argument + * @param callable(RequestInterface): Promise $first First middleware in the chain, used to to restart a request * * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception (The same as HttpAsyncClient) */ From d94c9324bc7d39f87c90486c27d728108343aef6 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 6 Jul 2020 21:39:10 +0100 Subject: [PATCH 080/123] Fixed typo --- src/Plugin.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Plugin.php b/src/Plugin.php index 99898b9..7c6cd58 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -6,6 +6,7 @@ use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; /** * A plugin is a middleware to transform the request and/or the response. @@ -24,8 +25,8 @@ interface Plugin * * @see http://docs.php-http.org/en/latest/plugins/build-your-own.html * - * @param callable(RequestInterface): Promise $next Next middleware in the chain, the request is passed as the first argument - * @param callable(RequestInterface): Promise $first First middleware in the chain, used to to restart a request + * @param callable(ResponseInterface): Promise $next Next middleware in the chain, the request is passed as the first argument + * @param callable(ResponseInterface): Promise $first First middleware in the chain, used to to restart a request * * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception (The same as HttpAsyncClient) */ From c751c7319d36b02a762d16f6fb319115d41d80d1 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 6 Jul 2020 22:05:43 +0100 Subject: [PATCH 081/123] Wasn't a typo... it's getting late :laugh: --- src/Plugin.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Plugin.php b/src/Plugin.php index 7c6cd58..99898b9 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -6,7 +6,6 @@ use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; /** * A plugin is a middleware to transform the request and/or the response. @@ -25,8 +24,8 @@ interface Plugin * * @see http://docs.php-http.org/en/latest/plugins/build-your-own.html * - * @param callable(ResponseInterface): Promise $next Next middleware in the chain, the request is passed as the first argument - * @param callable(ResponseInterface): Promise $first First middleware in the chain, used to to restart a request + * @param callable(RequestInterface): Promise $next Next middleware in the chain, the request is passed as the first argument + * @param callable(RequestInterface): Promise $first First middleware in the chain, used to to restart a request * * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception (The same as HttpAsyncClient) */ From 2895bd89d2e84395ae9411fb79af75dc53d6713c Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 13 Jul 2020 10:16:32 +0100 Subject: [PATCH 082/123] PHP 8.0 support --- .gitattributes | 1 - .github/workflows/tests.yml | 121 ++++++++++++++++++++++++++++++++++++ .travis.yml | 45 -------------- composer.json | 7 +-- 4 files changed, 124 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/tests.yml delete mode 100644 .travis.yml diff --git a/.gitattributes b/.gitattributes index c47225a..e6f2cb1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,7 +5,6 @@ /.php_cs export-ignore /.scrutinizer.yml export-ignore /.styleci.yml export-ignore -/.travis.yml export-ignore /behat.yml.dist export-ignore /features/ export-ignore /phpspec.ci.yml export-ignore diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..abfeb57 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,121 @@ +name: tests + +on: + push: + pull_request: + +jobs: + latest: + name: PHP ${{ matrix.php }} Latest + runs-on: ubuntu-latest + strategy: + matrix: + php: ['7.1', '7.2', '7.3', '7.4', '8.0'] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer:v2 + coverage: none + + - name: Install PHP 7 dependencies + run: composer update --prefer-dist --no-interaction --no-progress + if: "matrix.php != '8.0'" + + - name: Install PHP 8 dependencies + run: | + composer require "phpunit/phpunit:^9.3@dev" "phpunit/php-code-coverage:^9.0@dev" "sebastian/global-state:^5.0@dev" "phpdocumentor/reflection-docblock:^5.2@dev" --no-interaction --no-update + composer update --prefer-dist --no-interaction --no-progress --ignore-platform-req=php + if: "matrix.php == '8.0'" + + - name: Execute tests + run: composer test + + lowest: + name: PHP ${{ matrix.php }} Lowest + runs-on: ubuntu-latest + strategy: + matrix: + php: ['7.1', '7.2', '7.3', '7.4'] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer:v2 + coverage: none + + - name: Install dependencies + run: | + composer require "sebastian/comparator:^3.0.2" --no-interaction --no-update + composer update --prefer-dist --prefer-stable --prefer-lowest --no-interaction --no-progress + + - name: Execute tests + run: composer test + + symfony: + name: Symfony ${{ matrix.symfony }} LTS + runs-on: ubuntu-latest + strategy: + matrix: + symfony: ['2', '3', '4'] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.1 + tools: composer:v2 + coverage: none + + - name: Pin old packages + run: composer require "phpspec/phpspec:^2.5.8" --no-interaction --no-update + if: "matrix.symfony == '2'" + + - name: Install dependencies + run: | + composer require dunglas/symfony-lock:v${{ matrix.symfony }} --no-interaction --no-update + composer update --prefer-dist --no-interaction --prefer-stable --prefer-lowest --no-progress + + - name: Execute tests + run: composer test + + coverage: + name: Code Coverage + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + tools: composer:v2 + coverage: xdebug + + - name: Install dependencies + run: | + composer require "friends-of-phpspec/phpspec-code-coverage:^4.3.2" --no-interaction --no-update + composer update --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: composer test-ci + + - name: Upload coverage + 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 436011a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,45 +0,0 @@ -language: php -sudo: false - -cache: - directories: - - $HOME/.composer/cache/files - -jobs: - fast_finish: true - include: - - php: 7.1 - env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" - - php: 7.2 - env: COVERAGE=true DEPENDENCIES="friends-of-phpspec/phpspec-code-coverage:^4.3 phpspec/phpspec:^5.1" - script: - - composer test-ci - after_success: - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml --revision=$TRAVIS_COMMIT - # Test LTS versions - - php: 7.2 - env: DEPENDENCIES="dunglas/symfony-lock:^3" - - php: 7.2 - env: DEPENDENCIES="dunglas/symfony-lock:^4" - - php: 7.2 - env: TEST_COMMAND="./vendor/bin/phpunit" DEPENDENCIES="phpunit/phpunit:^7.5 nyholm/psr7:^1.0" - - # Latest dev release - - php: 7.4 - env: STABILITY="dev" - -before_install: -- if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi -- if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi; -- if ! [ -z "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi; - -install: -- cat composer.json -# To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355 -- if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi -- composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction - -script: -- composer validate --strict --no-check-lock -- composer test diff --git a/composer.json b/composer.json index 5f8e4bb..164c2b6 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": "^7.1", + "php": "^7.1 || ^8.0", "php-http/httplug": "^2.0", "php-http/message-factory": "^1.0", "php-http/message": "^1.6", @@ -26,9 +26,8 @@ "guzzlehttp/psr7": "^1.4", "nyholm/psr7": "^1.2", "phpspec/phpspec": "^5.1 || ^6.0", - "phpspec/prophecy": "^1.8", - "phpunit/phpunit": "^7.5", - "sebastian/comparator": "^3.0" + "phpspec/prophecy": "^1.10.2", + "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3" }, "suggest": { "ext-json": "To detect JSON responses with the ContentTypePlugin", From 070906fbad2dc8ef31b81a24f71c20a2a83a5cfa Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 13 Jul 2020 12:00:32 +0100 Subject: [PATCH 083/123] Release 2.2.1 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f612419..5aa7a4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 2.2.1 - 2020-07-13 + +### Fixed + +- Support for PHP 8 +- Plugin callable phpdoc + ## 2.2.0 - 2020-07-02 ### Added From 788697ea64192183b8e6c81f61cd19a66465d65c Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 12:05:00 +0100 Subject: [PATCH 084/123] Fix broken HttpMethodsClient with PSR RequestFactory (#202) --- composer.json | 2 +- src/HttpMethodsClient.php | 70 +++++++++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 164c2b6..7e746f0 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.2.x-dev" + "dev-master": "2.3.x-dev" } } } diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index bbe95be..26c253d 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -9,6 +9,9 @@ use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriInterface; final class HttpMethodsClient implements HttpMethodsClientInterface { @@ -22,10 +25,15 @@ final class HttpMethodsClient implements HttpMethodsClientInterface */ private $requestFactory; + /** + * @var StreamFactoryInterface|null + */ + private $streamFactory; + /** * @param RequestFactory|RequestFactoryInterface */ - public function __construct(ClientInterface $httpClient, $requestFactory) + public function __construct(ClientInterface $httpClient, $requestFactory, StreamFactoryInterface $streamFactory = null) { if (!$requestFactory instanceof RequestFactory && !$requestFactory instanceof RequestFactoryInterface) { throw new \TypeError( @@ -33,8 +41,13 @@ public function __construct(ClientInterface $httpClient, $requestFactory) ); } + if (!$requestFactory instanceof RequestFactory && null === $streamFactory) { + @trigger_error(sprintf('Passing a %s without a %s to %s::__construct() is deprecated as of version 2.3 and will be disallowed in version 3.0. A stream factory is required to create a request with a non-empty string body.', RequestFactoryInterface::class, StreamFactoryInterface::class, self::class)); + } + $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; + $this->streamFactory = $streamFactory; } public function get($uri, array $headers = []): ResponseInterface @@ -79,12 +92,55 @@ public function options($uri, array $headers = [], $body = null): ResponseInterf public function send(string $method, $uri, array $headers = [], $body = null): ResponseInterface { - return $this->sendRequest($this->requestFactory->createRequest( - $method, - $uri, - $headers, - $body - )); + if (!is_string($uri) && !$uri instanceof UriInterface) { + throw new \TypeError( + sprintf('%s::send(): Argument #2 ($uri) must be of type string|%s, %s given', self::class, UriInterface::class, get_debug_type($uri)) + ); + } + + if (!is_string($body) && !$body instanceof StreamInterface && null !== $body) { + throw new \TypeError( + sprintf('%s::send(): Argument #4 ($body) must be of type string|%s|null, %s given', self::class, StreamInterface::class, get_debug_type($body)) + ); + } + + return $this->sendRequest( + self::createRequest($method, $uri, $headers, $body) + ); + } + + /** + * @param string|UriInterface $uri + * @param string|StreamInterface|null $body + */ + private function createRequest(string $method, $uri, array $headers = [], $body = null): RequestInterface + { + if ($this->requestFactory instanceof RequestFactory) { + return $this->requestFactory->createRequest( + $method, + $uri, + $headers, + $body + ); + } + + if (is_string($body) && '' !== $body && null === $this->streamFactory) { + throw new \RuntimeException('Cannot create request: A stream factory is required to create a request with a non-empty string body.'); + } + + $request = $this->requestFactory->createRequest($method, $uri); + + foreach ($headers as $key => $value) { + $request = $request->withHeader($key, $value); + } + + if (null !== $body && '' !== $body) { + $request = $request->withBody( + is_string($body) ? $this->streamFactory->createStream($body) : $body + ); + } + + return $request; } public function sendRequest(RequestInterface $request): ResponseInterface From b102ff5b872958224f5ceacc87b219f8907f1103 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 20 Jul 2020 07:05:50 +0100 Subject: [PATCH 085/123] Static analysis fixes (#203) * Fixed bug in the cookie plugin * Fixed typo in phpdoc * Clarified plugin client factory type information * Added extra type information to help phpstan * Don't temprarily violate property types in the flexible http client * Removed redundent information * Cast null to string for date parsing * Added additional type to help phpstan * Run phpstan on actions --- .gitattributes | 29 +++++++++--------- .github/workflows/static.yml | 21 +++++++++++++ .gitignore | 1 + .php_cs.dist | 2 +- phpstan.neon.dist | 31 ++++++++++++++++++++ src/Deferred.php | 1 + src/Exception/HttpClientNoMatchException.php | 3 ++ src/FlexibleHttpClient.php | 12 ++------ src/HttpClientPool/HttpClientPool.php | 7 +++-- src/HttpClientRouter.php | 6 ++-- src/HttpMethodsClient.php | 2 +- src/Plugin/CookiePlugin.php | 6 ++-- src/Plugin/SeekableBodyPlugin.php | 6 ++++ src/PluginChain.php | 8 ++--- src/PluginClient.php | 14 +++++---- src/PluginClientBuilder.php | 3 ++ src/PluginClientFactory.php | 6 ++-- 17 files changed, 111 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/static.yml create mode 100644 phpstan.neon.dist diff --git a/.gitattributes b/.gitattributes index e6f2cb1..9e28cc7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,14 +1,15 @@ -.editorconfig export-ignore -.gitattributes export-ignore -/.github/ export-ignore -.gitignore export-ignore -/.php_cs export-ignore -/.scrutinizer.yml export-ignore -/.styleci.yml export-ignore -/behat.yml.dist export-ignore -/features/ export-ignore -/phpspec.ci.yml export-ignore -/phpspec.yml.dist export-ignore -/phpunit.xml.dist export-ignore -/spec/ export-ignore -/tests/ export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +/.github/ export-ignore +.gitignore export-ignore +/.php_cs export-ignore +/.scrutinizer.yml export-ignore +/.styleci.yml export-ignore +/behat.yml.dist export-ignore +/features/ export-ignore +/phpspec.ci.yml export-ignore +/phpspec.yml.dist export-ignore +/phpunit.xml.dist export-ignore +/phpstan.neon.dist export-ignore +/spec/ export-ignore +/tests/ export-ignore diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000..c8e6274 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,21 @@ +name: static + +on: + push: + pull_request: + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: PHPStan + uses: OskarStark/phpstan-ga@0.12.32 + env: + REQUIRE_DEV: false + with: + args: analyze --no-progress diff --git a/.gitignore b/.gitignore index 8ce13f3..bfae00b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /composer.lock /phpspec.yml /phpunit.xml +/phpstan.neon /vendor/ diff --git a/.php_cs.dist b/.php_cs.dist index 3a0d4fc..24e9bb9 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -18,7 +18,7 @@ return PhpCsFixer\Config::create() 'syntax' => 'short', ], 'no_empty_phpdoc' => true, - 'no_superfluous_phpdoc_tags' => true, + 'phpdoc_to_comment' => false, 'single_line_throw' => false, ]) ->setFinder($finder); diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..004d2f5 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,31 @@ +parameters: + level: max + checkMissingIterableValueType: false + treatPhpDocTypesAsCertain: false + paths: + - src + ignoreErrors: + - + message: "#^Strict comparison using \\!\\=\\= between null and null will always evaluate to false\\.$#" + count: 1 + path: src/HttpMethodsClient.php + + - + message: "#^Cannot call method createStream\\(\\) on Psr\\\\Http\\\\Message\\\\StreamFactoryInterface\\|null\\.$#" + count: 1 + path: src/HttpMethodsClient.php + + - + message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addSuccess\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/Plugin/Journal.php + + - + message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addFailure\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/Plugin/Journal.php + + - + message: "#^Call to an undefined method Http\\\\Client\\\\HttpAsyncClient\\:\\:sendRequest\\(\\)\\.$#" + count: 1 + path: src/PluginClient.php diff --git a/src/Deferred.php b/src/Deferred.php index 02fcc29..ef2b309 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -146,6 +146,7 @@ public function wait($unwrap = true) return $this->value; } + /** @var ClientExceptionInterface */ throw $this->failure; } } diff --git a/src/Exception/HttpClientNoMatchException.php b/src/Exception/HttpClientNoMatchException.php index 437467c..682c5dd 100644 --- a/src/Exception/HttpClientNoMatchException.php +++ b/src/Exception/HttpClientNoMatchException.php @@ -14,6 +14,9 @@ */ final class HttpClientNoMatchException extends TransferException { + /** + * @var RequestInterface + */ private $request; public function __construct(string $message, RequestInterface $request, \Exception $previous = null) diff --git a/src/FlexibleHttpClient.php b/src/FlexibleHttpClient.php index 1a602c4..c1e327f 100644 --- a/src/FlexibleHttpClient.php +++ b/src/FlexibleHttpClient.php @@ -30,15 +30,7 @@ public function __construct($client) ); } - $this->httpClient = $client; - $this->httpAsyncClient = $client; - - if (!($this->httpClient instanceof ClientInterface)) { - $this->httpClient = new EmulatedHttpClient($this->httpClient); - } - - if (!($this->httpAsyncClient instanceof HttpAsyncClient)) { - $this->httpAsyncClient = new EmulatedHttpAsyncClient($this->httpAsyncClient); - } + $this->httpClient = $client instanceof ClientInterface ? $client : new EmulatedHttpClient($client); + $this->httpAsyncClient = $client instanceof HttpAsyncClient ? $client : new EmulatedHttpAsyncClient($client); } } diff --git a/src/HttpClientPool/HttpClientPool.php b/src/HttpClientPool/HttpClientPool.php index 0665aa7..d6d1777 100644 --- a/src/HttpClientPool/HttpClientPool.php +++ b/src/HttpClientPool/HttpClientPool.php @@ -25,13 +25,14 @@ abstract class HttpClientPool implements HttpClientPoolInterface /** * Add a client to the pool. * - * @param ClientInterface|HttpAsyncClient|HttpClientPoolItem $client + * @param ClientInterface|HttpAsyncClient $client */ public function addHttpClient($client): void { - if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient && !$client instanceof HttpClientPoolItem) { + // no need to check for HttpClientPoolItem here, since it extends the other interfaces + if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) { throw new \TypeError( - sprintf('%s::addHttpClient(): Argument #1 ($client) must be of type %s|%s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, HttpClientPoolItem::class, get_debug_type($client)) + sprintf('%s::addHttpClient(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client)) ); } diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index 78a4e69..040d893 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -19,7 +19,7 @@ final class HttpClientRouter implements HttpClientRouterInterface { /** - * @var array + * @var (array{matcher: RequestMatcher, client: FlexibleHttpClient})[] */ private $clients = []; @@ -60,10 +60,8 @@ public function addClient($client, RequestMatcher $requestMatcher): void /** * Choose an HTTP client given a specific request. - * - * @return ClientInterface|HttpAsyncClient */ - private function chooseHttpClient(RequestInterface $request) + private function chooseHttpClient(RequestInterface $request): FlexibleHttpClient { foreach ($this->clients as $client) { if ($client['matcher']->matches($request)) { diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index 26c253d..f62b187 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -31,7 +31,7 @@ final class HttpMethodsClient implements HttpMethodsClientInterface private $streamFactory; /** - * @param RequestFactory|RequestFactoryInterface + * @param RequestFactory|RequestFactoryInterface $requestFactory */ public function __construct(ClientInterface $httpClient, $requestFactory, StreamFactoryInterface $streamFactory = null) { diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index 2ec5265..d0c862b 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -117,7 +117,7 @@ private function createCookie(RequestInterface $request, string $setCookieHeader switch (strtolower($key)) { case 'expires': try { - $expires = CookieUtil::parseDate($value); + $expires = CookieUtil::parseDate((string) $value); } catch (UnexpectedValueException $e) { throw new TransferException( sprintf( @@ -167,13 +167,13 @@ private function createCookie(RequestInterface $request, string $setCookieHeader * * @param string $part A single cookie value in format key=value * - * @return string[] + * @return array{0:string, 1:?string} */ private function createValueKey(string $part): array { $parts = explode('=', $part, 2); $key = trim($parts[0]); - $value = isset($parts[1]) ? trim($parts[1]) : true; + $value = isset($parts[1]) ? trim($parts[1]) : null; return [$key, $value]; } diff --git a/src/Plugin/SeekableBodyPlugin.php b/src/Plugin/SeekableBodyPlugin.php index 3b37b1c..5c20af3 100644 --- a/src/Plugin/SeekableBodyPlugin.php +++ b/src/Plugin/SeekableBodyPlugin.php @@ -12,8 +12,14 @@ */ abstract class SeekableBodyPlugin implements Plugin { + /** + * @var bool + */ protected $useFileBuffer; + /** + * @var int + */ protected $memoryBufferSize; /** diff --git a/src/PluginChain.php b/src/PluginChain.php index 220ba36..db72332 100644 --- a/src/PluginChain.php +++ b/src/PluginChain.php @@ -14,7 +14,7 @@ final class PluginChain /** @var Plugin[] */ private $plugins; - /** @var callable */ + /** @var callable(RequestInterface): Promise */ private $clientCallable; /** @var int */ @@ -24,9 +24,9 @@ final class PluginChain private $restarts = 0; /** - * @param Plugin[] $plugins A plugin chain - * @param callable $clientCallable Callable making the HTTP call - * @param array $options { + * @param Plugin[] $plugins A plugin chain + * @param callable(RequestInterface): Promise $clientCallable Callable making the HTTP call + * @param array $options { * * @var int $max_restarts * } diff --git a/src/PluginClient.php b/src/PluginClient.php index 0d330b1..f3c9b23 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -9,6 +9,7 @@ use Http\Client\HttpClient; use Http\Client\Promise\HttpFulfilledPromise; use Http\Client\Promise\HttpRejectedPromise; +use Http\Promise\Promise; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -24,7 +25,7 @@ final class PluginClient implements HttpClient, HttpAsyncClient /** * An HTTP async client. * - * @var HttpAsyncClient|HttpClient + * @var HttpAsyncClient */ private $client; @@ -71,13 +72,13 @@ public function __construct($client, array $plugins = [], array $options = []) */ public function sendRequest(RequestInterface $request): ResponseInterface { - // If we don't have an http client, use the async call - if (!($this->client instanceof ClientInterface)) { + // If the client doesn't support sync calls, call async + if (!$this->client instanceof ClientInterface) { return $this->sendAsyncRequest($request)->wait(); } - // Else we want to use the synchronous call of the underlying client, and not the async one in the case - // we have both an async and sync call + // Else we want to use the synchronous call of the underlying client, + // and not the async one in the case we have both an async and sync call $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) { try { return new HttpFulfilledPromise($this->client->sendRequest($request)); @@ -121,9 +122,12 @@ private function configure(array $options = []): array * * @param Plugin[] $plugins A plugin chain * @param callable $clientCallable Callable making the HTTP call + * + * @return callable(RequestInterface): Promise */ private function createPluginChain(array $plugins, callable $clientCallable): callable { + /** @var callable(RequestInterface): Promise */ return new PluginChain($plugins, $clientCallable, $this->options); } } diff --git a/src/PluginClientBuilder.php b/src/PluginClientBuilder.php index 87ee280..8746498 100644 --- a/src/PluginClientBuilder.php +++ b/src/PluginClientBuilder.php @@ -30,6 +30,9 @@ public function addPlugin(Plugin $plugin, int $priority = 0): self return $this; } + /** + * @param mixed $value + */ public function setOption(string $name, $value): self { $this->options[$name] = $value; diff --git a/src/PluginClientFactory.php b/src/PluginClientFactory.php index bbdc5c3..0c91fc3 100644 --- a/src/PluginClientFactory.php +++ b/src/PluginClientFactory.php @@ -16,7 +16,7 @@ final class PluginClientFactory { /** - * @var callable|null + * @var (callable(ClientInterface|HttpAsyncClient, Plugin[], array): PluginClient)|null */ private static $factory; @@ -28,8 +28,10 @@ final class PluginClientFactory * application execution. * * @internal + * + * @param callable(ClientInterface|HttpAsyncClient, Plugin[], array): PluginClient $factory */ - public static function setFactory(callable $factory) + public static function setFactory(callable $factory): void { static::$factory = $factory; } From e97af1deb5496d8b3b1b4add1e2b92806d37580a Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 20 Jul 2020 09:10:37 +0100 Subject: [PATCH 086/123] Release 2.3.0 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aa7a4b..1a7b201 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 2.3.0 - 2020-07-21 + +### Fixed + +- HttpMethodsClient with PSR RequestFactory +- Bug in the cookie plugin with empty cookies +- Bug when parsing null-valued date headers + +### Changed + +- Deprecation when constructing a HttpMethodsClient with PSR RequestFactory but without a StreamFactory + ## 2.2.1 - 2020-07-13 ### Fixed From c75450153e49f225765b738d566b2951f8b5871a Mon Sep 17 00:00:00 2001 From: Olexiy Kyrychenko Date: Sat, 3 Jul 2021 08:23:55 +0200 Subject: [PATCH 087/123] Added `strict` option for RedirectPlugin not to modify request method on statuses 300, 301, 302 (#208) --- CHANGELOG.md | 18 ++++++++---- spec/Plugin/RedirectPluginSpec.php | 47 +++++++++++++++++++++++++++++- src/Plugin/RedirectPlugin.php | 11 ++++++- 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a7b201..dccc2f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 2.4.0 (unreleased) + +### Added + +- `strict` option to `RedirectPlugin` to allow preserving the request method on redirections with status 300, 301 and 302. + ## 2.3.0 - 2020-07-21 ### Fixed @@ -112,7 +118,7 @@ ## 1.7.0 - 2017-11-30 -### Added +### Added - Symfony 4 support @@ -132,12 +138,12 @@ ### Changed -- The `RetryPlugin` does now wait between retries. To disable/change this feature you must write something like: - +- The `RetryPlugin` does now wait between retries. To disable/change this feature you must write something like: + ```php -$plugin = new RetryPlugin(['delay' => function(RequestInterface $request, Exception $e, $retries) { - return 0; -}); +$plugin = new RetryPlugin(['delay' => function(RequestInterface $request, Exception $e, $retries) { + return 0; +}); ``` ### Deprecated diff --git a/spec/Plugin/RedirectPluginSpec.php b/spec/Plugin/RedirectPluginSpec.php index 3342f60..475d458 100644 --- a/spec/Plugin/RedirectPluginSpec.php +++ b/spec/Plugin/RedirectPluginSpec.php @@ -286,11 +286,56 @@ public function it_switch_method_for_302( $request->withUri($uriRedirect)->willReturn($modifiedRequest); $modifiedRequest->getUri()->willReturn($uriRedirect); + $uriRedirect->__toString()->willReturn('/redirect'); + $modifiedRequest->getMethod()->willReturn('POST'); + $modifiedRequest->withMethod('GET')->shouldBeCalled()->willReturn($modifiedRequest); + + $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { + if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { + return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); + } + }; + + $first = function (RequestInterface $receivedRequest) use ($modifiedRequest, $promise) { + if (Argument::is($modifiedRequest->getWrappedObject())->scoreArgument($receivedRequest)) { + return $promise->getWrappedObject(); + } + }; + + $promise->getState()->willReturn(Promise::FULFILLED); + $promise->wait()->shouldBeCalled()->willReturn($finalResponse); + + $this->handleRequest($request, $next, $first); + } + + public function it_does_not_switch_method_for_302_with_strict_option( + UriInterface $uri, + UriInterface $uriRedirect, + RequestInterface $request, + ResponseInterface $responseRedirect, + RequestInterface $modifiedRequest, + ResponseInterface $finalResponse, + Promise $promise + ) { + $this->beConstructedWith(['strict' => true]); + + $request->getUri()->willReturn($uri); + $uri->__toString()->willReturn('/original'); + $responseRedirect->getStatusCode()->willReturn(302); + $responseRedirect->hasHeader('Location')->willReturn(true); + $responseRedirect->getHeaderLine('Location')->willReturn('/redirect'); + + $request->getUri()->willReturn($uri); + $uri->withPath('/redirect')->willReturn($uriRedirect); + $uriRedirect->withFragment('')->willReturn($uriRedirect); + $uriRedirect->withQuery('')->willReturn($uriRedirect); + + $request->withUri($uriRedirect)->willReturn($modifiedRequest); $modifiedRequest->getUri()->willReturn($uriRedirect); $uriRedirect->__toString()->willReturn('/redirect'); $modifiedRequest->getMethod()->willReturn('POST'); - $modifiedRequest->withMethod('GET')->willReturn($modifiedRequest); + $modifiedRequest->withMethod('GET')->shouldNotBeCalled(); $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index 061f35b..488b3aa 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -105,7 +105,8 @@ final class RedirectPlugin implements Plugin * @param array $config { * * @var bool|string[] $preserve_header True keeps all headers, false remove all of them, an array is interpreted as a list of header names to keep - * @var bool $use_default_for_multiple Whether the location header must be directly used for a multiple redirection status code (300). + * @var bool $use_default_for_multiple Whether the location header must be directly used for a multiple redirection status code (300) + * @var bool $strict When true, redirect codes 300, 301, 302 will not modify request method and body. * } */ public function __construct(array $config = []) @@ -114,9 +115,11 @@ public function __construct(array $config = []) $resolver->setDefaults([ 'preserve_header' => true, 'use_default_for_multiple' => true, + 'strict' => false, ]); $resolver->setAllowedTypes('preserve_header', ['bool', 'array']); $resolver->setAllowedTypes('use_default_for_multiple', 'bool'); + $resolver->setAllowedTypes('strict', 'bool'); $resolver->setNormalizer('preserve_header', function (OptionsResolver $resolver, $value) { if (is_bool($value) && false === $value) { return []; @@ -128,6 +131,12 @@ public function __construct(array $config = []) $this->preserveHeader = $options['preserve_header']; $this->useDefaultForMultiple = $options['use_default_for_multiple']; + + if ($options['strict']) { + $this->redirectCodes[300]['switch'] = false; + $this->redirectCodes[301]['switch'] = false; + $this->redirectCodes[302]['switch'] = false; + } } /** From 4274000bf8fa2ac8c49f1f0a8cd5636f0182416d Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sat, 3 Jul 2021 08:39:17 +0200 Subject: [PATCH 088/123] fix phpstan errors --- phpstan.neon.dist | 6 +++--- src/BatchResult.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 004d2f5..ac324e6 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -6,14 +6,14 @@ parameters: - src ignoreErrors: - - message: "#^Strict comparison using \\!\\=\\= between null and null will always evaluate to false\\.$#" + message: "#^Cannot call method createStream\\(\\) on Psr\\\\Http\\\\Message\\\\StreamFactoryInterface\\|null\\.$#" count: 1 path: src/HttpMethodsClient.php - - message: "#^Cannot call method createStream\\(\\) on Psr\\\\Http\\\\Message\\\\StreamFactoryInterface\\|null\\.$#" + message: "#^Cannot call method isDisabled() on Http\\Client\\Common\\HttpClientPool\\HttpClientPoolItem|false\\.$#" count: 1 - path: src/HttpMethodsClient.php + path: src/HttpClientPool/RoundRobinClientPool.php - message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addSuccess\\(\\) has no return typehint specified\\.$#" diff --git a/src/BatchResult.php b/src/BatchResult.php index 45e1535..ccaf83c 100644 --- a/src/BatchResult.php +++ b/src/BatchResult.php @@ -16,12 +16,12 @@ final class BatchResult { /** - * @var \SplObjectStorage + * @var \SplObjectStorage */ private $responses; /** - * @var \SplObjectStorage + * @var \SplObjectStorage */ private $exceptions; From 29e0c60d982f04017069483e832b92074d0a90b2 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Mon, 5 Jul 2021 10:19:25 +0200 Subject: [PATCH 089/123] prepare release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dccc2f0..7c4e5df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 2.4.0 (unreleased) +## 2.4.0 - 2021-07-05 ### Added From e2796c4084acebb69bf6c0159fd4f8c905399c73 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Mon, 5 Jul 2021 10:21:34 +0200 Subject: [PATCH 090/123] phpstan fixed a wrong warning, no longer need to ignore it --- phpstan.neon.dist | 5 ----- 1 file changed, 5 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index ac324e6..1373517 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,11 +10,6 @@ parameters: count: 1 path: src/HttpMethodsClient.php - - - message: "#^Cannot call method isDisabled() on Http\\Client\\Common\\HttpClientPool\\HttpClientPoolItem|false\\.$#" - count: 1 - path: src/HttpClientPool/RoundRobinClientPool.php - - message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addSuccess\\(\\) has no return typehint specified\\.$#" count: 1 From 133e14e5a1ad8be62a160ec7748328cb7e3a88ec Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 18 Oct 2021 07:58:57 +0100 Subject: [PATCH 091/123] Test on PHP 8.1 (#214) --- .github/workflows/tests.yml | 15 +++++++-------- composer.json | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index abfeb57..e8eabc3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0'] + php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] steps: - name: Checkout code @@ -23,15 +23,14 @@ jobs: tools: composer:v2 coverage: none - - name: Install PHP 7 dependencies - run: composer update --prefer-dist --no-interaction --no-progress - if: "matrix.php != '8.0'" - - - name: Install PHP 8 dependencies + - name: Require PHPSpec 7.1 dependencies run: | - composer require "phpunit/phpunit:^9.3@dev" "phpunit/php-code-coverage:^9.0@dev" "sebastian/global-state:^5.0@dev" "phpdocumentor/reflection-docblock:^5.2@dev" --no-interaction --no-update + composer require "phpspec/phpspec:^7.1@dev" --no-interaction --no-update composer update --prefer-dist --no-interaction --no-progress --ignore-platform-req=php - if: "matrix.php == '8.0'" + if: "matrix.php == '8.1'" + + - name: Install PHP dependencies + run: composer update --prefer-dist --no-interaction --no-progress - name: Execute tests run: composer test diff --git a/composer.json b/composer.json index 7e746f0..091a1d9 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "doctrine/instantiator": "^1.1", "guzzlehttp/psr7": "^1.4", "nyholm/psr7": "^1.2", - "phpspec/phpspec": "^5.1 || ^6.0", + "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", "phpspec/prophecy": "^1.10.2", "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3" }, From cb83bfedfee722d66cef57e7f408f1f132e1dec9 Mon Sep 17 00:00:00 2001 From: Jeroen <1517978+Jeroeny@users.noreply.github.com> Date: Thu, 25 Nov 2021 16:14:30 +0100 Subject: [PATCH 092/123] Dependency: Symfony/options-resolver allow ^6.0 (#215) * composer dependency: symfony/options-resolver allow ^6.0 * add symfony 5 and 6 to build matrix * use flex to lock symfony version * use flex to lock symfony version --- .github/workflows/tests.yml | 5 +++-- .gitignore | 2 ++ composer.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e8eabc3..41475b0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -66,7 +66,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - symfony: ['2', '3', '4'] + symfony: ['4', '5', '6'] steps: - name: Checkout code @@ -85,7 +85,8 @@ jobs: - name: Install dependencies run: | - composer require dunglas/symfony-lock:v${{ matrix.symfony }} --no-interaction --no-update + composer require --no-update --no-interaction --no-progress symfony/flex + composer config extra.symfony.require ${{ matrix.symfony}} composer update --prefer-dist --no-interaction --prefer-stable --prefer-lowest --no-progress - name: Execute tests diff --git a/.gitignore b/.gitignore index bfae00b..1cb59e9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ /phpunit.xml /phpstan.neon /vendor/ + +.phpunit.result.cache diff --git a/composer.json b/composer.json index 091a1d9..eb5cb4c 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.0", - "symfony/options-resolver": "^2.6 || ^3.4.20 || ~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0", + "symfony/options-resolver": "~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0 || ^6.0", "symfony/polyfill-php80": "^1.17" }, "require-dev": { From 16d90d19cff7cd23884d45c3153f6b0496f55910 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 25 Nov 2021 16:43:07 +0100 Subject: [PATCH 093/123] cleanups found with phpstan --- composer.json | 2 +- phpstan.neon.dist | 31 ++++++++++++++++++++++++++----- src/HttpMethodsClient.php | 8 ++++---- src/Plugin/AddHostPlugin.php | 6 +++--- src/Plugin/ContentTypePlugin.php | 8 ++++---- src/Plugin/CookiePlugin.php | 2 +- src/Plugin/DecoderPlugin.php | 6 +++--- src/Plugin/ErrorPlugin.php | 6 +++--- src/Plugin/RedirectPlugin.php | 14 ++++++-------- src/Plugin/RetryPlugin.php | 16 +++++++++------- src/Plugin/SeekableBodyPlugin.php | 8 ++++---- src/PluginChain.php | 5 +---- src/PluginClient.php | 4 +--- src/PluginClientFactory.php | 8 ++++---- 14 files changed, 70 insertions(+), 54 deletions(-) diff --git a/composer.json b/composer.json index eb5cb4c..2ea45aa 100644 --- a/composer.json +++ b/composer.json @@ -13,8 +13,8 @@ "require": { "php": "^7.1 || ^8.0", "php-http/httplug": "^2.0", - "php-http/message-factory": "^1.0", "php-http/message": "^1.6", + "php-http/message-factory": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.0", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 1373517..328ca33 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -5,18 +5,19 @@ parameters: paths: - src ignoreErrors: + # Exception still thrown in PHP 8, not sure why phpstan complains - - message: "#^Cannot call method createStream\\(\\) on Psr\\\\Http\\\\Message\\\\StreamFactoryInterface\\|null\\.$#" - count: 1 - path: src/HttpMethodsClient.php + message: "#^Dead catch - UnexpectedValueException is never thrown in the try block\\.$#" + count: 2 + path: src/BatchResult.php - - message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addSuccess\\(\\) has no return typehint specified\\.$#" + message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addSuccess\\(\\) has no return type specified\\.$#" count: 1 path: src/Plugin/Journal.php - - message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addFailure\\(\\) has no return typehint specified\\.$#" + message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addFailure\\(\\) has no return type specified\\.$#" count: 1 path: src/Plugin/Journal.php @@ -24,3 +25,23 @@ parameters: message: "#^Call to an undefined method Http\\\\Client\\\\HttpAsyncClient\\:\\:sendRequest\\(\\)\\.$#" count: 1 path: src/PluginClient.php + + - + message: "#^Method Http\\\\Client\\\\Common\\\\EmulatedHttpClient\\:\\:sendRequest\\(\\) should return Psr\\\\Http\\\\Message\\\\ResponseInterface but returns mixed\\.$#" + count: 1 + path: src/EmulatedHttpClient.php + + - + message: "#^Anonymous function should return Psr\\\\Http\\\\Message\\\\ResponseInterface but returns mixed\\.$#" + count: 1 + path: src/Plugin/RedirectPlugin.php + + - + message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\RetryPlugin\\:\\:retry\\(\\) should return Psr\\\\Http\\\\Message\\\\ResponseInterface but returns mixed\\.$#" + count: 1 + path: src/Plugin/RetryPlugin.php + + - + message: "#^Method Http\\\\Client\\\\Common\\\\PluginClient\\:\\:sendRequest\\(\\) should return Psr\\\\Http\\\\Message\\\\ResponseInterface but returns mixed\\.$#" + count: 2 + path: src/PluginClient.php diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index f62b187..497921b 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -124,10 +124,6 @@ private function createRequest(string $method, $uri, array $headers = [], $body ); } - if (is_string($body) && '' !== $body && null === $this->streamFactory) { - throw new \RuntimeException('Cannot create request: A stream factory is required to create a request with a non-empty string body.'); - } - $request = $this->requestFactory->createRequest($method, $uri); foreach ($headers as $key => $value) { @@ -135,6 +131,10 @@ private function createRequest(string $method, $uri, array $headers = [], $body } if (null !== $body && '' !== $body) { + if (null === $this->streamFactory) { + throw new \RuntimeException('Cannot create request: A stream factory is required to create a request with a non-empty string body.'); + } + $request = $request->withBody( is_string($body) ? $this->streamFactory->createStream($body) : $body ); diff --git a/src/Plugin/AddHostPlugin.php b/src/Plugin/AddHostPlugin.php index 33ede17..c7fb05a 100644 --- a/src/Plugin/AddHostPlugin.php +++ b/src/Plugin/AddHostPlugin.php @@ -28,10 +28,10 @@ final class AddHostPlugin implements Plugin private $replace; /** - * @param array $config { + * @param array{'replace'?: bool} $config * - * @var bool $replace True will replace all hosts, false will only add host when none is specified. - * } + * Configuration options: + * - replace: True will replace all hosts, false will only add host when none is specified. */ public function __construct(UriInterface $host, array $config = []) { diff --git a/src/Plugin/ContentTypePlugin.php b/src/Plugin/ContentTypePlugin.php index c3fb25c..9a87f99 100644 --- a/src/Plugin/ContentTypePlugin.php +++ b/src/Plugin/ContentTypePlugin.php @@ -35,11 +35,11 @@ final class ContentTypePlugin implements Plugin private $sizeLimit; /** - * @param array $config { + * @param array{'skip_detection'?: bool, 'size_limit'?: int} $config * - * @var bool $skip_detection true skip detection if stream size is bigger than $size_limit - * @var int $size_limit size stream limit for which the detection as to be skipped. - * } + * Configuration options: + * - skip_detection: true skip detection if stream size is bigger than $size_limit + * - size_limit: size stream limit for which the detection as to be skipped. */ public function __construct(array $config = []) { diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index d0c862b..aa4d5d7 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -97,7 +97,7 @@ private function createCookie(RequestInterface $request, string $setCookieHeader { $parts = array_map('trim', explode(';', $setCookieHeader)); - if (empty($parts) || !strpos($parts[0], '=')) { + if ('' === $parts[0] || false === strpos($parts[0], '=')) { return null; } diff --git a/src/Plugin/DecoderPlugin.php b/src/Plugin/DecoderPlugin.php index 271ad3c..b685967 100644 --- a/src/Plugin/DecoderPlugin.php +++ b/src/Plugin/DecoderPlugin.php @@ -31,10 +31,10 @@ final class DecoderPlugin implements Plugin private $useContentEncoding; /** - * @param array $config { + * @param array{'use_content_encoding'?: bool} $config * - * @var bool $use_content_encoding Whether this plugin should look at the Content-Encoding header first or only at the Transfer-Encoding (defaults to true). - * } + * Configuration options: + * - use_content_encoding: Whether this plugin should look at the Content-Encoding header first or only at the Transfer-Encoding (defaults to true). */ public function __construct(array $config = []) { diff --git a/src/Plugin/ErrorPlugin.php b/src/Plugin/ErrorPlugin.php index a187295..06c795f 100644 --- a/src/Plugin/ErrorPlugin.php +++ b/src/Plugin/ErrorPlugin.php @@ -37,10 +37,10 @@ final class ErrorPlugin implements Plugin private $onlyServerException; /** - * @param array $config { + * @param array{'only_server_exception'?: bool} $config * - * @var bool only_server_exception Whether this plugin should only throw 5XX Exceptions (default to false). - * } + * Configuration options: + * - only_server_exception: Whether this plugin should only throw 5XX Exceptions (default to false). */ public function __construct(array $config = []) { diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index 488b3aa..5b45826 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -102,12 +102,12 @@ final class RedirectPlugin implements Plugin private $circularDetection = []; /** - * @param array $config { + * @param array{'preserve_header'?: bool|string[], 'use_default_for_multiple'?: bool, 'strict'?: bool} $config * - * @var bool|string[] $preserve_header True keeps all headers, false remove all of them, an array is interpreted as a list of header names to keep - * @var bool $use_default_for_multiple Whether the location header must be directly used for a multiple redirection status code (300) - * @var bool $strict When true, redirect codes 300, 301, 302 will not modify request method and body. - * } + * Configuration options: + * - preserve_header: True keeps all headers, false remove all of them, an array is interpreted as a list of header names to keep + * - use_default_for_multiple: Whether the location header must be directly used for a multiple redirection status code (300) + * - strict: When true, redirect codes 300, 301, 302 will not modify request method and body. */ public function __construct(array $config = []) { @@ -182,9 +182,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl } // Call redirect request synchronously - $redirectPromise = $first($redirectRequest); - - return $redirectPromise->wait(); + return $first($redirectRequest)->wait(); }); } diff --git a/src/Plugin/RetryPlugin.php b/src/Plugin/RetryPlugin.php index 92b7cbf..144679e 100644 --- a/src/Plugin/RetryPlugin.php +++ b/src/Plugin/RetryPlugin.php @@ -56,14 +56,14 @@ final class RetryPlugin implements Plugin private $retryStorage = []; /** - * @param array $config { + * @param array{'retries'?: int, 'error_response_decider'?: callable, 'exception_decider'?: callable, 'error_response_delay'?: callable, 'exception_delay'?: callable} $config * - * @var int $retries Number of retries to attempt if an exception occurs before letting the exception bubble up - * @var callable $error_response_decider A callback that gets a request and response to decide whether the request should be retried - * @var callable $exception_decider A callback that gets a request and an exception to decide after a failure whether the request should be retried - * @var callable $error_response_delay A callback that gets a request and response and the current number of retries and returns how many microseconds we should wait before trying again - * @var callable $exception_delay A callback that gets a request, an exception and the current number of retries and returns how many microseconds we should wait before trying again - * } + * Configuration options: + * - retries: Number of retries to attempt if an exception occurs before letting the exception bubble up + * - error_response_decider: A callback that gets a request and response to decide whether the request should be retried + * - exception_decider: A callback that gets a request and an exception to decide after a failure whether the request should be retried + * - error_response_delay: A callback that gets a request and response and the current number of retries and returns how many microseconds we should wait before trying again + * - exception_delay: A callback that gets a request, an exception and the current number of retries and returns how many microseconds we should wait before trying again */ public function __construct(array $config = []) { @@ -115,6 +115,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl } if (call_user_func($this->errorResponseDecider, $request, $response)) { + /** @var int $time */ $time = call_user_func($this->errorResponseDelay, $request, $response, $this->retryStorage[$chainIdentifier]); $response = $this->retry($request, $next, $first, $chainIdentifier, $time); } @@ -139,6 +140,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl throw $exception; } + /** @var int $time */ $time = call_user_func($this->exceptionDelay, $request, $exception, $this->retryStorage[$chainIdentifier]); return $this->retry($request, $next, $first, $chainIdentifier, $time); diff --git a/src/Plugin/SeekableBodyPlugin.php b/src/Plugin/SeekableBodyPlugin.php index 5c20af3..1be2cde 100644 --- a/src/Plugin/SeekableBodyPlugin.php +++ b/src/Plugin/SeekableBodyPlugin.php @@ -23,11 +23,11 @@ abstract class SeekableBodyPlugin implements Plugin protected $memoryBufferSize; /** - * @param array $config { + * @param array{'use_file_buffer'?: bool, 'memory_boffer_size'?: int} $config * - * @var bool $use_file_buffer Whether this plugin should use a file as a buffer if the stream is too big, defaults to true - * @var int $memory_buffer_size Max memory size in bytes to use for the buffer before it use a file, defaults to 2097152 (2 mb) - * } + * Configuration options: + * - use_file_buffer: Whether this plugin should use a file as a buffer if the stream is too big, defaults to true + * - memory_buffer_size: Max memory size in bytes to use for the buffer before it use a file, defaults to 2097152 (2 mb) */ public function __construct(array $config = []) { diff --git a/src/PluginChain.php b/src/PluginChain.php index db72332..fcb1914 100644 --- a/src/PluginChain.php +++ b/src/PluginChain.php @@ -26,10 +26,7 @@ final class PluginChain /** * @param Plugin[] $plugins A plugin chain * @param callable(RequestInterface): Promise $clientCallable Callable making the HTTP call - * @param array $options { - * - * @var int $max_restarts - * } + * @param array{'max_restarts'?: int} $options */ public function __construct(array $plugins, callable $clientCallable, array $options = []) { diff --git a/src/PluginClient.php b/src/PluginClient.php index 48bafab..d67145d 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -46,9 +46,7 @@ final class PluginClient implements HttpClient, HttpAsyncClient /** * @param ClientInterface|HttpAsyncClient $client An HTTP async client * @param Plugin[] $plugins A plugin chain - * @param array $options { - * @var int $max_restarts - * } + * @param array{'max_restarts'?: int} $options */ public function __construct($client, array $plugins = [], array $options = []) { diff --git a/src/PluginClientFactory.php b/src/PluginClientFactory.php index 0c91fc3..f7e93e3 100644 --- a/src/PluginClientFactory.php +++ b/src/PluginClientFactory.php @@ -39,11 +39,11 @@ public static function setFactory(callable $factory): void /** * @param ClientInterface|HttpAsyncClient $client * @param Plugin[] $plugins - * @param array $options { + * @param array{'client_name'?: string} $options * - * @var string $client_name to give client a name which may be used when displaying client information like in - * the HTTPlugBundle profiler. - * } + * Configuration options: + * - client_name: to give client a name which may be used when displaying client information + * like in the HTTPlugBundle profiler. * * @see PluginClient constructor for PluginClient specific $options. */ From d135751167d57e27c74de674d6a30cef2dc8e054 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 26 Nov 2021 16:01:24 +0100 Subject: [PATCH 094/123] prepare release --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfa42cc..9b031de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Change Log +## 2.5.0 - 2021-11-26 + +### Added + +- Support for Symfony 6 +- Support for PHP 8.1 + +### Changed + +- Dropped support for Symfony 2 and 3 - please keep using version 2.4.0 of this library if you can't update Symfony. + ## 2.4.0 - 2021-07-05 ### Added From 36d48c0982840e5c2caa655da62cadb646744964 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 21 Sep 2022 08:13:17 +0200 Subject: [PATCH 095/123] allow flex plugin in composer configuration --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 41475b0..678f13f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -85,6 +85,7 @@ jobs: - name: Install dependencies run: | + composer config --no-plugins allow-plugins.symfony/flex true composer require --no-update --no-interaction --no-progress symfony/flex composer config extra.symfony.require ${{ matrix.symfony}} composer update --prefer-dist --no-interaction --prefer-stable --prefer-lowest --no-progress From c06f154c2553a2013f35485dfc3013792f43dc0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Sat, 10 Sep 2022 12:10:35 +0200 Subject: [PATCH 096/123] RedirectPlugin: Default to empty path when Location doesn't specify any --- CHANGELOG.md | 6 ++++ spec/Plugin/RedirectPluginSpec.php | 6 ++++ src/Plugin/RedirectPlugin.php | 21 +++---------- tests/Plugin/RedirectPluginTest.php | 48 +++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 tests/Plugin/RedirectPluginTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b031de..596ad94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 2.5.1 - 2022-09-?? + +### Fixed + +- Fixes false positive circular detection in RedirectPlugin in cases when target location does not contain path + ## 2.5.0 - 2021-11-26 ### Added diff --git a/spec/Plugin/RedirectPluginSpec.php b/spec/Plugin/RedirectPluginSpec.php index 475d458..2682499 100644 --- a/spec/Plugin/RedirectPluginSpec.php +++ b/spec/Plugin/RedirectPluginSpec.php @@ -162,6 +162,9 @@ public function it_replace_full_url( $request->getUri()->willReturn($uri); $uri->withScheme('https')->willReturn($uriRedirect); + $uri->withPath('/redirect')->willReturn($uri); + $uri->withQuery('query')->willReturn($uri); + $uri->withFragment('fragment')->willReturn($uri); $uriRedirect->withHost('server.com')->willReturn($uriRedirect); $uriRedirect->withPort('8000')->willReturn($uriRedirect); $uriRedirect->withPath('/redirect')->willReturn($uriRedirect); @@ -520,6 +523,9 @@ public function it_redirects_http_to_https( $request->getUri()->willReturn($uri); $request->withUri($uriRedirect)->willReturn($modifiedRequest); $uri->__toString()->willReturn('http://my-site.com/original'); + $uri->withPath('/original')->willReturn($uri); + $uri->withFragment('')->willReturn($uri); + $uri->withQuery('')->willReturn($uri); $uri->withScheme('https')->willReturn($uriRedirect); $uriRedirect->withHost('my-site.com')->willReturn($uriRedirect); diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index 5b45826..d5e52f8 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -231,6 +231,11 @@ private function createUri(ResponseInterface $redirectResponse, RequestInterface } $uri = $originalRequest->getUri(); + $uri = $uri + ->withPath(array_key_exists('path', $parsedLocation) ? $parsedLocation['path'] : '') + ->withQuery(array_key_exists('query', $parsedLocation) ? $parsedLocation['query'] : '') + ->withFragment(array_key_exists('fragment', $parsedLocation) ? $parsedLocation['fragment'] : '') + ; if (array_key_exists('scheme', $parsedLocation)) { $uri = $uri->withScheme($parsedLocation['scheme']); @@ -244,22 +249,6 @@ private function createUri(ResponseInterface $redirectResponse, RequestInterface $uri = $uri->withPort($parsedLocation['port']); } - if (array_key_exists('path', $parsedLocation)) { - $uri = $uri->withPath($parsedLocation['path']); - } - - if (array_key_exists('query', $parsedLocation)) { - $uri = $uri->withQuery($parsedLocation['query']); - } else { - $uri = $uri->withQuery(''); - } - - if (array_key_exists('fragment', $parsedLocation)) { - $uri = $uri->withFragment($parsedLocation['fragment']); - } else { - $uri = $uri->withFragment(''); - } - return $uri; } } diff --git a/tests/Plugin/RedirectPluginTest.php b/tests/Plugin/RedirectPluginTest.php new file mode 100644 index 0000000..18cd230 --- /dev/null +++ b/tests/Plugin/RedirectPluginTest.php @@ -0,0 +1,48 @@ +expectException(CircularRedirectionException::class); + (new RedirectPlugin())->handleRequest( + new Request('GET', 'https://example.com/path?query=value'), + function () { + return new FulfilledPromise(new Response(302, ['Location' => 'https://example.com/path?query=value'])); + }, + function () {} + )->wait(); + } + + /** + * @testWith ["https://example.com/path?query=value", "https://example.com?query=value", "https://example.com?query=value"] + * ["https://example.com/path?query=value", "https://example.com/?query=value", "https://example.com/?query=value"] + * ["https://example.com", "https://example.com?query=value", "https://example.com?query=value"] + */ + public function testTargetUriMappingFromLocationHeader(string $originalUri, string $locationUri, string $targetUri): void + { + $response = (new RedirectPlugin())->handleRequest( + new Request('GET', $originalUri), + function () use ($locationUri) { + return new FulfilledPromise(new Response(302, ['Location' => $locationUri])); + }, + function (RequestInterface $request) { + return new FulfilledPromise(new Response(200, ['uri' => $request->getUri()->__toString()])); + } + )->wait(); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals($targetUri, $response->getHeaderLine('uri')); + } +} From 262bc0a026d354f4715e0ddd61bbb37bd262ec5e Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Tue, 27 Sep 2022 12:01:32 +0200 Subject: [PATCH 097/123] redirection to different domain must not keep previous port --- .github/workflows/static.yml | 2 ++ .github/workflows/tests.yml | 2 ++ CHANGELOG.md | 3 +- spec/Plugin/RedirectPluginSpec.php | 50 ----------------------------- src/Plugin/RedirectPlugin.php | 34 ++++++++++++++++++-- tests/Plugin/RedirectPluginTest.php | 22 +++++++++++-- 6 files changed, 56 insertions(+), 57 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index c8e6274..ec70168 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -2,6 +2,8 @@ name: static on: push: + branches: + - master pull_request: jobs: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 678f13f..df61234 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,6 +2,8 @@ name: tests on: push: + branches: + - master pull_request: jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 596ad94..9ff3bd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### Fixed -- Fixes false positive circular detection in RedirectPlugin in cases when target location does not contain path +- [RedirectPlugin] Fixed handling of redirection to different domain with default port +- [RedirectPlugin] Fixed false positive circular detection in RedirectPlugin in cases when target location does not contain path ## 2.5.0 - 2021-11-26 diff --git a/spec/Plugin/RedirectPluginSpec.php b/spec/Plugin/RedirectPluginSpec.php index 2682499..a8df45c 100644 --- a/spec/Plugin/RedirectPluginSpec.php +++ b/spec/Plugin/RedirectPluginSpec.php @@ -506,54 +506,4 @@ public function it_throws_circular_redirection_exception_on_alternating_redirect $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); $promise->shouldThrow(CircularRedirectionException::class)->duringWait(); } - - public function it_redirects_http_to_https( - UriInterface $uri, - UriInterface $uriRedirect, - RequestInterface $request, - ResponseInterface $responseRedirect, - RequestInterface $modifiedRequest, - ResponseInterface $finalResponse, - Promise $promise - ) { - $responseRedirect->getStatusCode()->willReturn(302); - $responseRedirect->hasHeader('Location')->willReturn(true); - $responseRedirect->getHeaderLine('Location')->willReturn('https://my-site.com/original'); - - $request->getUri()->willReturn($uri); - $request->withUri($uriRedirect)->willReturn($modifiedRequest); - $uri->__toString()->willReturn('http://my-site.com/original'); - $uri->withPath('/original')->willReturn($uri); - $uri->withFragment('')->willReturn($uri); - $uri->withQuery('')->willReturn($uri); - - $uri->withScheme('https')->willReturn($uriRedirect); - $uriRedirect->withHost('my-site.com')->willReturn($uriRedirect); - $uriRedirect->withPath('/original')->willReturn($uriRedirect); - $uriRedirect->withFragment('')->willReturn($uriRedirect); - $uriRedirect->withQuery('')->willReturn($uriRedirect); - $uriRedirect->__toString()->willReturn('https://my-site.com/original'); - - $modifiedRequest->getUri()->willReturn($uriRedirect); - $modifiedRequest->getMethod()->willReturn('GET'); - - $next = function (RequestInterface $receivedRequest) use ($request, $responseRedirect) { - if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) { - return new HttpFulfilledPromise($responseRedirect->getWrappedObject()); - } - }; - - $first = function (RequestInterface $receivedRequest) use ($modifiedRequest, $promise) { - if (Argument::is($modifiedRequest->getWrappedObject())->scoreArgument($receivedRequest)) { - return $promise->getWrappedObject(); - } - }; - - $promise->getState()->willReturn(Promise::FULFILLED); - $promise->wait()->shouldBeCalled()->willReturn($finalResponse); - - $finalPromise = $this->handleRequest($request, $next, $first); - $finalPromise->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); - $finalPromise->wait()->shouldReturn($finalResponse); - } } diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index d5e52f8..8f7805a 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -226,13 +226,39 @@ private function createUri(ResponseInterface $redirectResponse, RequestInterface $location = $redirectResponse->getHeaderLine('Location'); $parsedLocation = parse_url($location); - if (false === $parsedLocation) { - throw new HttpException(sprintf('Location %s could not be parsed', $location), $originalRequest, $redirectResponse); + if (false === $parsedLocation || '' === $location) { + throw new HttpException(sprintf('Location "%s" could not be parsed', $location), $originalRequest, $redirectResponse); } $uri = $originalRequest->getUri(); + + // Redirections can either use an absolute uri or a relative reference https://www.rfc-editor.org/rfc/rfc3986#section-4.2 + // If relative, we need to check if we have an absolute path or not + + $path = array_key_exists('path', $parsedLocation) ? $parsedLocation['path'] : ''; + if (!array_key_exists('host', $parsedLocation) && '/' !== $location[0]) { + // the target is a relative-path reference, we need to merge it with the base path + $originalPath = $uri->getPath(); + if ('' === $path) { + $path = $originalPath; + } elseif (($pos = strrpos($originalPath, '/')) !== false) { + $path = substr($originalPath, 0, $pos+1).$path; + } else { + $path = '/'.$path; + } + /* replace '/./' or '/foo/../' with '/' */ + $re = ['#(/\./)#', '#/(?!\.\.)[^/]+/\.\./#']; + for ($n = 1; $n > 0; $path = preg_replace($re, '/', $path, -1, $n)) { + if (null === $path) { + throw new HttpException(sprintf('Failed to resolve Location %s', $location), $originalRequest, $redirectResponse); + } + } + } + if (null === $path) { + throw new HttpException(sprintf('Failed to resolve Location %s', $location), $originalRequest, $redirectResponse); + } $uri = $uri - ->withPath(array_key_exists('path', $parsedLocation) ? $parsedLocation['path'] : '') + ->withPath($path) ->withQuery(array_key_exists('query', $parsedLocation) ? $parsedLocation['query'] : '') ->withFragment(array_key_exists('fragment', $parsedLocation) ? $parsedLocation['fragment'] : '') ; @@ -247,6 +273,8 @@ private function createUri(ResponseInterface $redirectResponse, RequestInterface if (array_key_exists('port', $parsedLocation)) { $uri = $uri->withPort($parsedLocation['port']); + } elseif (array_key_exists('host', $parsedLocation)) { + $uri = $uri->withPort(null); } return $uri; diff --git a/tests/Plugin/RedirectPluginTest.php b/tests/Plugin/RedirectPluginTest.php index 18cd230..1e0ff05 100644 --- a/tests/Plugin/RedirectPluginTest.php +++ b/tests/Plugin/RedirectPluginTest.php @@ -26,10 +26,26 @@ function () {} )->wait(); } + public function provideRedirections(): array + { + return [ + 'no path on target' => ["https://example.com/path?query=value", "https://example.com?query=value", "https://example.com?query=value"], + 'root path on target' => ["https://example.com/path?query=value", "https://example.com/?query=value", "https://example.com/?query=value"], + 'redirect to query' => ["https://example.com", "https://example.com?query=value", "https://example.com?query=value"], + 'redirect to different domain without port' => ["https://example.com:8000", "https://foo.com?query=value", "https://foo.com?query=value"], + 'network-path redirect, preserve scheme' => ["https://example.com:8000", "//foo.com/path?query=value", "https://foo.com/path?query=value"], + 'absolute-path redirect, preserve host' => ["https://example.com:8000", "/path?query=value", "https://example.com:8000/path?query=value"], + 'relative-path redirect, append' => ["https://example.com:8000/path/", "sub/path?query=value", "https://example.com:8000/path/sub/path?query=value"], + 'relative-path on non-folder' => ["https://example.com:8000/path/foo", "sub/path?query=value", "https://example.com:8000/path/sub/path?query=value"], + 'relative-path moving up' => ["https://example.com:8000/path/", "../other?query=value", "https://example.com:8000/other?query=value"], + 'relative-path with ./' => ["https://example.com:8000/path/", "./other?query=value", "https://example.com:8000/path/other?query=value"], + 'relative-path with //' => ["https://example.com:8000/path/", "other//sub?query=value", "https://example.com:8000/path/other//sub?query=value"], + 'relative-path redirect with only query' => ["https://example.com:8000/path", "?query=value", "https://example.com:8000/path?query=value"], + ]; + } + /** - * @testWith ["https://example.com/path?query=value", "https://example.com?query=value", "https://example.com?query=value"] - * ["https://example.com/path?query=value", "https://example.com/?query=value", "https://example.com/?query=value"] - * ["https://example.com", "https://example.com?query=value", "https://example.com?query=value"] + * @dataProvider provideRedirections */ public function testTargetUriMappingFromLocationHeader(string $originalUri, string $locationUri, string $targetUri): void { From 509e513b17e97d624fc3f9eb16a70cf473e49ecd Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 28 Sep 2022 17:12:57 +0200 Subject: [PATCH 098/123] add php-cs-fixer step to build --- .github/workflows/static.yml | 13 +++++++++++++ .gitignore | 4 ++-- .php-cs-fixer.dist.php | 18 ++++++++++++++++++ src/Deferred.php | 5 ++++- src/HttpClientPool/HttpClientPool.php | 4 ++-- src/Plugin/AddHostPlugin.php | 2 +- src/Plugin/AddPathPlugin.php | 2 +- src/Plugin/ContentTypePlugin.php | 2 +- src/Plugin/DecoderPlugin.php | 2 +- src/Plugin/ErrorPlugin.php | 6 +++--- src/Plugin/RedirectPlugin.php | 4 ++-- src/PluginChain.php | 1 + src/PluginClient.php | 1 - src/PluginClientFactory.php | 4 ++-- tests/Plugin/RedirectPluginTest.php | 25 +++++++++++++------------ tests/PluginChainTest.php | 4 +--- 16 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 .php-cs-fixer.dist.php diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index ec70168..fb1e01a 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -21,3 +21,16 @@ jobs: REQUIRE_DEV: false with: args: analyze --no-progress + + php-cs-fixer: + name: PHP-CS-Fixer + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: PHP-CS-Fixer + uses: docker://oskarstark/php-cs-fixer-ga + with: + args: --dry-run --diff diff --git a/.gitignore b/.gitignore index 1cb59e9..5874147 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -/.php_cs -/.php_cs.cache +/.php-cs-fixer.php +/.php-cs-fixer.cache /behat.yml /build/ /composer.lock diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..92bc748 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,18 @@ +in(__DIR__.'/src') + ->in(__DIR__.'/tests') + ->name('*.php') +; + +$config = new PhpCsFixer\Config(); + +return $config + ->setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + 'single_line_throw' => false, + ]) + ->setFinder($finder) +; diff --git a/src/Deferred.php b/src/Deferred.php index ef2b309..646f375 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -146,7 +146,10 @@ public function wait($unwrap = true) return $this->value; } - /** @var ClientExceptionInterface */ + if (null === $this->failure) { + throw new \RuntimeException('Internal Error: Promise is not fulfilled but has no exception stored'); + } + throw $this->failure; } } diff --git a/src/HttpClientPool/HttpClientPool.php b/src/HttpClientPool/HttpClientPool.php index d6d1777..a30bdb0 100644 --- a/src/HttpClientPool/HttpClientPool.php +++ b/src/HttpClientPool/HttpClientPool.php @@ -46,9 +46,9 @@ public function addHttpClient($client): void /** * Return an http client given a specific strategy. * - * @throws HttpClientNotFoundException When no http client has been found into the pool - * * @return HttpClientPoolItem Return a http client that can do both sync or async + * + * @throws HttpClientNotFoundException When no http client has been found into the pool */ abstract protected function chooseHttpClient(): HttpClientPoolItem; diff --git a/src/Plugin/AddHostPlugin.php b/src/Plugin/AddHostPlugin.php index c7fb05a..2a866ee 100644 --- a/src/Plugin/AddHostPlugin.php +++ b/src/Plugin/AddHostPlugin.php @@ -31,7 +31,7 @@ final class AddHostPlugin implements Plugin * @param array{'replace'?: bool} $config * * Configuration options: - * - replace: True will replace all hosts, false will only add host when none is specified. + * - replace: True will replace all hosts, false will only add host when none is specified */ public function __construct(UriInterface $host, array $config = []) { diff --git a/src/Plugin/AddPathPlugin.php b/src/Plugin/AddPathPlugin.php index 9d43104..87fcdce 100644 --- a/src/Plugin/AddPathPlugin.php +++ b/src/Plugin/AddPathPlugin.php @@ -70,7 +70,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl if (substr($path, 0, strlen($prepend)) !== $prepend) { $request = $request->withUri($request->getUri() ->withPath($prepend.$path) - ); + ); } return $next($request); diff --git a/src/Plugin/ContentTypePlugin.php b/src/Plugin/ContentTypePlugin.php index 9a87f99..da3758e 100644 --- a/src/Plugin/ContentTypePlugin.php +++ b/src/Plugin/ContentTypePlugin.php @@ -39,7 +39,7 @@ final class ContentTypePlugin implements Plugin * * Configuration options: * - skip_detection: true skip detection if stream size is bigger than $size_limit - * - size_limit: size stream limit for which the detection as to be skipped. + * - size_limit: size stream limit for which the detection as to be skipped */ public function __construct(array $config = []) { diff --git a/src/Plugin/DecoderPlugin.php b/src/Plugin/DecoderPlugin.php index b685967..41c1a58 100644 --- a/src/Plugin/DecoderPlugin.php +++ b/src/Plugin/DecoderPlugin.php @@ -34,7 +34,7 @@ final class DecoderPlugin implements Plugin * @param array{'use_content_encoding'?: bool} $config * * Configuration options: - * - use_content_encoding: Whether this plugin should look at the Content-Encoding header first or only at the Transfer-Encoding (defaults to true). + * - use_content_encoding: Whether this plugin should look at the Content-Encoding header first or only at the Transfer-Encoding (defaults to true) */ public function __construct(array $config = []) { diff --git a/src/Plugin/ErrorPlugin.php b/src/Plugin/ErrorPlugin.php index 06c795f..678977f 100644 --- a/src/Plugin/ErrorPlugin.php +++ b/src/Plugin/ErrorPlugin.php @@ -40,7 +40,7 @@ final class ErrorPlugin implements Plugin * @param array{'only_server_exception'?: bool} $config * * Configuration options: - * - only_server_exception: Whether this plugin should only throw 5XX Exceptions (default to false). + * - only_server_exception: Whether this plugin should only throw 5XX Exceptions (default to false) */ public function __construct(array $config = []) { @@ -72,10 +72,10 @@ public function handleRequest(RequestInterface $request, callable $next, callabl * @param RequestInterface $request Request of the call * @param ResponseInterface $response Response of the call * + * @return ResponseInterface If status code is not in 4xx or 5xx return response + * * @throws ClientErrorException If response status code is a 4xx * @throws ServerErrorException If response status code is a 5xx - * - * @return ResponseInterface If status code is not in 4xx or 5xx return response */ private function transformResponseToException(RequestInterface $request, ResponseInterface $response): ResponseInterface { diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index 8f7805a..7b96b1b 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -107,7 +107,7 @@ final class RedirectPlugin implements Plugin * Configuration options: * - preserve_header: True keeps all headers, false remove all of them, an array is interpreted as a list of header names to keep * - use_default_for_multiple: Whether the location header must be directly used for a multiple redirection status code (300) - * - strict: When true, redirect codes 300, 301, 302 will not modify request method and body. + * - strict: When true, redirect codes 300, 301, 302 will not modify request method and body */ public function __construct(array $config = []) { @@ -242,7 +242,7 @@ private function createUri(ResponseInterface $redirectResponse, RequestInterface if ('' === $path) { $path = $originalPath; } elseif (($pos = strrpos($originalPath, '/')) !== false) { - $path = substr($originalPath, 0, $pos+1).$path; + $path = substr($originalPath, 0, $pos + 1).$path; } else { $path = '/'.$path; } diff --git a/src/PluginChain.php b/src/PluginChain.php index fcb1914..2dd630c 100644 --- a/src/PluginChain.php +++ b/src/PluginChain.php @@ -5,6 +5,7 @@ namespace Http\Client\Common; use function array_reverse; + use Http\Client\Common\Exception\LoopException; use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; diff --git a/src/PluginClient.php b/src/PluginClient.php index d67145d..db81243 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -124,7 +124,6 @@ private function configure(array $options = []): array */ private function createPluginChain(array $plugins, callable $clientCallable): callable { - /** @var callable(RequestInterface): Promise */ return new PluginChain($plugins, $clientCallable, $this->options); } } diff --git a/src/PluginClientFactory.php b/src/PluginClientFactory.php index f7e93e3..1d2b2be 100644 --- a/src/PluginClientFactory.php +++ b/src/PluginClientFactory.php @@ -39,11 +39,11 @@ public static function setFactory(callable $factory): void /** * @param ClientInterface|HttpAsyncClient $client * @param Plugin[] $plugins - * @param array{'client_name'?: string} $options + * @param array{'client_name'?: string} $options * * Configuration options: * - client_name: to give client a name which may be used when displaying client information - * like in the HTTPlugBundle profiler. + * like in the HTTPlugBundle profiler * * @see PluginClient constructor for PluginClient specific $options. */ diff --git a/tests/Plugin/RedirectPluginTest.php b/tests/Plugin/RedirectPluginTest.php index 1e0ff05..5e26703 100644 --- a/tests/Plugin/RedirectPluginTest.php +++ b/tests/Plugin/RedirectPluginTest.php @@ -1,4 +1,5 @@ ["https://example.com/path?query=value", "https://example.com?query=value", "https://example.com?query=value"], - 'root path on target' => ["https://example.com/path?query=value", "https://example.com/?query=value", "https://example.com/?query=value"], - 'redirect to query' => ["https://example.com", "https://example.com?query=value", "https://example.com?query=value"], - 'redirect to different domain without port' => ["https://example.com:8000", "https://foo.com?query=value", "https://foo.com?query=value"], - 'network-path redirect, preserve scheme' => ["https://example.com:8000", "//foo.com/path?query=value", "https://foo.com/path?query=value"], - 'absolute-path redirect, preserve host' => ["https://example.com:8000", "/path?query=value", "https://example.com:8000/path?query=value"], - 'relative-path redirect, append' => ["https://example.com:8000/path/", "sub/path?query=value", "https://example.com:8000/path/sub/path?query=value"], - 'relative-path on non-folder' => ["https://example.com:8000/path/foo", "sub/path?query=value", "https://example.com:8000/path/sub/path?query=value"], - 'relative-path moving up' => ["https://example.com:8000/path/", "../other?query=value", "https://example.com:8000/other?query=value"], - 'relative-path with ./' => ["https://example.com:8000/path/", "./other?query=value", "https://example.com:8000/path/other?query=value"], - 'relative-path with //' => ["https://example.com:8000/path/", "other//sub?query=value", "https://example.com:8000/path/other//sub?query=value"], - 'relative-path redirect with only query' => ["https://example.com:8000/path", "?query=value", "https://example.com:8000/path?query=value"], + 'no path on target' => ['https://example.com/path?query=value', 'https://example.com?query=value', 'https://example.com?query=value'], + 'root path on target' => ['https://example.com/path?query=value', 'https://example.com/?query=value', 'https://example.com/?query=value'], + 'redirect to query' => ['https://example.com', 'https://example.com?query=value', 'https://example.com?query=value'], + 'redirect to different domain without port' => ['https://example.com:8000', 'https://foo.com?query=value', 'https://foo.com?query=value'], + 'network-path redirect, preserve scheme' => ['https://example.com:8000', '//foo.com/path?query=value', 'https://foo.com/path?query=value'], + 'absolute-path redirect, preserve host' => ['https://example.com:8000', '/path?query=value', 'https://example.com:8000/path?query=value'], + 'relative-path redirect, append' => ['https://example.com:8000/path/', 'sub/path?query=value', 'https://example.com:8000/path/sub/path?query=value'], + 'relative-path on non-folder' => ['https://example.com:8000/path/foo', 'sub/path?query=value', 'https://example.com:8000/path/sub/path?query=value'], + 'relative-path moving up' => ['https://example.com:8000/path/', '../other?query=value', 'https://example.com:8000/other?query=value'], + 'relative-path with ./' => ['https://example.com:8000/path/', './other?query=value', 'https://example.com:8000/path/other?query=value'], + 'relative-path with //' => ['https://example.com:8000/path/', 'other//sub?query=value', 'https://example.com:8000/path/other//sub?query=value'], + 'relative-path redirect with only query' => ['https://example.com:8000/path', '?query=value', 'https://example.com:8000/path?query=value'], ]; } diff --git a/tests/PluginChainTest.php b/tests/PluginChainTest.php index 95fdfab..1c8d29a 100644 --- a/tests/PluginChainTest.php +++ b/tests/PluginChainTest.php @@ -9,15 +9,13 @@ use Http\Client\Common\PluginChain; use Http\Promise\Promise; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; use Psr\Http\Message\RequestInterface; class PluginChainTest extends TestCase { private function createPlugin(callable $func): Plugin { - return new class ($func) implements Plugin - { + return new class($func) implements Plugin { public $func; public function __construct(callable $func) From 92b7425ccda1c15547605704723f4639f4cd2d65 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 29 Sep 2022 09:53:58 +0200 Subject: [PATCH 099/123] prepare release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ff3bd8..020d740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 2.5.1 - 2022-09-?? +## 2.5.1 - 2022-09-29 ### Fixed From a48935c90a01cf2fa4e7715c76a16ec3f39082e8 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 29 Sep 2022 09:48:13 +0200 Subject: [PATCH 100/123] remove body on redirection --- CHANGELOG.md | 7 +++ phpstan.neon.dist | 12 +++++ spec/Plugin/RedirectPluginSpec.php | 9 +++- src/Plugin/RedirectPlugin.php | 68 +++++++++++++++++++++++++++-- tests/Plugin/RedirectPluginTest.php | 53 +++++++++++++++++++++- 5 files changed, 144 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 020d740..e795fce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 2.6.0 - 2022-09-29 + +- [RedirectPlugin] Redirection of non GET/HEAD requests with a body now removes the body on follow-up requests, if the + HTTP method changes. To do this, the plugin needs to find a PSR-7 stream implementation. If none is found, you can + explicitly pass a PSR-17 StreamFactoryInterface in the `stream_factory` option. + To keep sending the body in all cases, set the `stream_factory` option to null explicitly. + ## 2.5.1 - 2022-09-29 ### Fixed diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 328ca33..236087f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -36,6 +36,18 @@ parameters: count: 1 path: src/Plugin/RedirectPlugin.php + # phpstan is confused by the optional dependencies. we check for existence first + - + message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\RedirectPlugin::guessStreamFactory\\(\\) should return Psr\\\\Http\\\\Message\\\\StreamFactoryInterface\\|null but returns Nyholm\\\\Psr7\\\\Factory\\\\Psr17Factory\\.$#" + count: 1 + path: src/Plugin/RedirectPlugin.php + + # phpstan is confused by the optional dependencies. we check for existence first + - + message: "#^Call to static method streamFor\\(\\) on an unknown class GuzzleHttp\\\\Psr7\\\\Utils\\.$#" + count: 1 + path: src/Plugin/RedirectPlugin.php + - message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\RetryPlugin\\:\\:retry\\(\\) should return Psr\\\\Http\\\\Message\\\\ResponseInterface but returns mixed\\.$#" count: 1 diff --git a/spec/Plugin/RedirectPluginSpec.php b/spec/Plugin/RedirectPluginSpec.php index a8df45c..9bcfb7f 100644 --- a/spec/Plugin/RedirectPluginSpec.php +++ b/spec/Plugin/RedirectPluginSpec.php @@ -37,6 +37,7 @@ public function it_redirects_on_302( ResponseInterface $finalResponse, Promise $promise ) { + $this->beConstructedWith(['stream_factory' => null]); $responseRedirect->getStatusCode()->willReturn(302); $responseRedirect->hasHeader('Location')->willReturn(true); $responseRedirect->getHeaderLine('Location')->willReturn('/redirect'); @@ -81,6 +82,7 @@ public function it_use_storage_on_301( ResponseInterface $finalResponse, ResponseInterface $redirectResponse ) { + $this->beConstructedWith(['stream_factory' => null]); $request->getUri()->willReturn($uri); $uri->__toString()->willReturn('/original'); $uri->withPath('/redirect')->willReturn($uriRedirect); @@ -153,6 +155,7 @@ public function it_replace_full_url( ResponseInterface $finalResponse, Promise $promise ) { + $this->beConstructedWith(['stream_factory' => null]); $request->getUri()->willReturn($uri); $uri->__toString()->willReturn('/original'); @@ -275,6 +278,7 @@ public function it_switch_method_for_302( ResponseInterface $finalResponse, Promise $promise ) { + $this->beConstructedWith(['stream_factory' => null]); $request->getUri()->willReturn($uri); $uri->__toString()->willReturn('/original'); @@ -367,7 +371,10 @@ public function it_clears_headers( ResponseInterface $finalResponse, Promise $promise ) { - $this->beConstructedWith(['preserve_header' => ['Accept']]); + $this->beConstructedWith([ + 'preserve_header' => ['Accept'], + 'stream_factory' => null, + ]); $request->getUri()->willReturn($uri); $uri->__toString()->willReturn('/original'); diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index 7b96b1b..ee5c232 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -4,14 +4,20 @@ namespace Http\Client\Common\Plugin; +use GuzzleHttp\Psr7\Utils; use Http\Client\Common\Exception\CircularRedirectionException; use Http\Client\Common\Exception\MultipleRedirectionException; use Http\Client\Common\Plugin; use Http\Client\Exception\HttpException; +use Http\Discovery\Psr17FactoryDiscovery; use Http\Promise\Promise; +use Nyholm\Psr7\Factory\Psr17Factory; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; /** @@ -101,6 +107,11 @@ final class RedirectPlugin implements Plugin */ private $circularDetection = []; + /** + * @var StreamFactoryInterface|null + */ + private $streamFactory; + /** * @param array{'preserve_header'?: bool|string[], 'use_default_for_multiple'?: bool, 'strict'?: bool} $config * @@ -108,6 +119,7 @@ final class RedirectPlugin implements Plugin * - preserve_header: True keeps all headers, false remove all of them, an array is interpreted as a list of header names to keep * - use_default_for_multiple: Whether the location header must be directly used for a multiple redirection status code (300) * - strict: When true, redirect codes 300, 301, 302 will not modify request method and body + * - stream_factory: If set, must be a PSR-17 StreamFactoryInterface - if not set, we try to discover one */ public function __construct(array $config = []) { @@ -116,10 +128,12 @@ public function __construct(array $config = []) 'preserve_header' => true, 'use_default_for_multiple' => true, 'strict' => false, + 'stream_factory' => null, ]); $resolver->setAllowedTypes('preserve_header', ['bool', 'array']); $resolver->setAllowedTypes('use_default_for_multiple', 'bool'); $resolver->setAllowedTypes('strict', 'bool'); + $resolver->setAllowedTypes('stream_factory', [StreamFactoryInterface::class, 'null']); $resolver->setNormalizer('preserve_header', function (OptionsResolver $resolver, $value) { if (is_bool($value) && false === $value) { return []; @@ -127,6 +141,9 @@ public function __construct(array $config = []) return $value; }); + $resolver->setDefault('stream_factory', function (Options $options): ?StreamFactoryInterface { + return $this->guessStreamFactory(); + }); $options = $resolver->resolve($config); $this->preserveHeader = $options['preserve_header']; @@ -137,6 +154,8 @@ public function __construct(array $config = []) $this->redirectCodes[301]['switch'] = false; $this->redirectCodes[302]['switch'] = false; } + + $this->streamFactory = $options['stream_factory']; } /** @@ -170,7 +189,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl $this->circularDetection[$chainIdentifier][] = (string) $request->getUri(); - if (in_array((string) $redirectRequest->getUri(), $this->circularDetection[$chainIdentifier])) { + if (in_array((string) $redirectRequest->getUri(), $this->circularDetection[$chainIdentifier], true)) { throw new CircularRedirectionException('Circular redirection detected', $request, $response); } @@ -186,19 +205,62 @@ public function handleRequest(RequestInterface $request, callable $next, callabl }); } + /** + * The default only needs to be determined if no value is provided. + */ + public function guessStreamFactory(): ?StreamFactoryInterface + { + if (class_exists(Psr17FactoryDiscovery::class)) { + try { + return Psr17FactoryDiscovery::findStreamFactory(); + } catch (\Throwable $t) { + // ignore and try other options + } + } + if (class_exists(Psr17Factory::class)) { + return new Psr17Factory(); + } + if (class_exists(Utils::class)) { + return new class() implements StreamFactoryInterface { + public function createStream(string $content = ''): StreamInterface + { + return Utils::streamFor($content); + } + + public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface + { + throw new \RuntimeException('Internal error: this method should not be needed'); + } + + public function createStreamFromResource($resource): StreamInterface + { + throw new \RuntimeException('Internal error: this method should not be needed'); + } + }; + } + + return null; + } + private function buildRedirectRequest(RequestInterface $originalRequest, UriInterface $targetUri, int $statusCode): RequestInterface { $originalRequest = $originalRequest->withUri($targetUri); - if (false !== $this->redirectCodes[$statusCode]['switch'] && !in_array($originalRequest->getMethod(), $this->redirectCodes[$statusCode]['switch']['unless'])) { + if (false !== $this->redirectCodes[$statusCode]['switch'] && !in_array($originalRequest->getMethod(), $this->redirectCodes[$statusCode]['switch']['unless'], true)) { $originalRequest = $originalRequest->withMethod($this->redirectCodes[$statusCode]['switch']['to']); + if ('GET' === $this->redirectCodes[$statusCode]['switch']['to'] && $this->streamFactory) { + // if we found a stream factory, remove the request body. otherwise leave the body there. + $originalRequest = $originalRequest->withoutHeader('content-type'); + $originalRequest = $originalRequest->withoutHeader('content-length'); + $originalRequest = $originalRequest->withBody($this->streamFactory->createStream()); + } } if (is_array($this->preserveHeader)) { $headers = array_keys($originalRequest->getHeaders()); foreach ($headers as $name) { - if (!in_array($name, $this->preserveHeader)) { + if (!in_array($name, $this->preserveHeader, true)) { $originalRequest = $originalRequest->withoutHeader($name); } } diff --git a/tests/Plugin/RedirectPluginTest.php b/tests/Plugin/RedirectPluginTest.php index 5e26703..9211bee 100644 --- a/tests/Plugin/RedirectPluginTest.php +++ b/tests/Plugin/RedirectPluginTest.php @@ -7,6 +7,7 @@ use Http\Client\Common\Exception\CircularRedirectionException; use Http\Client\Common\Plugin\RedirectPlugin; use Http\Promise\FulfilledPromise; +use Nyholm\Psr7\Factory\Psr17Factory; use Nyholm\Psr7\Request; use Nyholm\Psr7\Response; use PHPUnit\Framework\TestCase; @@ -27,6 +28,56 @@ function () {} )->wait(); } + public function testPostGetDropRequestBody(): void + { + $response = (new RedirectPlugin())->handleRequest( + new Request('POST', 'https://example.com/path', ['Content-Type' => 'text/plain', 'Content-Length' => '10'], (new Psr17Factory())->createStream('hello test')), + function (RequestInterface $request) { + $this->assertSame(10, $request->getBody()->getSize()); + $this->assertTrue($request->hasHeader('Content-Type')); + $this->assertTrue($request->hasHeader('Content-Length')); + + return new FulfilledPromise(new Response(302, ['Location' => 'https://example.com/other'])); + }, + function (RequestInterface $request) { + $this->assertSame('GET', $request->getMethod()); + $this->assertSame(0, $request->getBody()->getSize()); + $this->assertFalse($request->hasHeader('Content-Type')); + $this->assertFalse($request->hasHeader('Content-Length')); + + return new FulfilledPromise(new Response(200, ['uri' => $request->getUri()->__toString()])); + } + )->wait(); + + $this->assertSame('https://example.com/other', $response->getHeaderLine('uri')); + } + + public function testPostGetNoFactory(): void + { + // We explicitly set the stream factory to null. Same happens if no factory can be found. + // In this case, the redirect will leave the body alone. + $response = (new RedirectPlugin(['stream_factory' => null]))->handleRequest( + new Request('POST', 'https://example.com/path', ['Content-Type' => 'text/plain', 'Content-Length' => '10'], (new Psr17Factory())->createStream('hello test')), + function (RequestInterface $request) { + $this->assertSame(10, $request->getBody()->getSize()); + $this->assertTrue($request->hasHeader('Content-Type')); + $this->assertTrue($request->hasHeader('Content-Length')); + + return new FulfilledPromise(new Response(302, ['Location' => 'https://example.com/other'])); + }, + function (RequestInterface $request) { + $this->assertSame('GET', $request->getMethod()); + $this->assertSame(10, $request->getBody()->getSize()); + $this->assertTrue($request->hasHeader('Content-Type')); + $this->assertTrue($request->hasHeader('Content-Length')); + + return new FulfilledPromise(new Response(200, ['uri' => $request->getUri()->__toString()])); + } + )->wait(); + + $this->assertSame('https://example.com/other', $response->getHeaderLine('uri')); + } + public function provideRedirections(): array { return [ @@ -60,6 +111,6 @@ function (RequestInterface $request) { } )->wait(); $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertEquals($targetUri, $response->getHeaderLine('uri')); + $this->assertSame($targetUri, $response->getHeaderLine('uri')); } } From 3e8542a6d8a72a00188acfe46d65290e98acc915 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 11 Apr 2023 11:25:17 +0200 Subject: [PATCH 101/123] Allow "psr/http-message" v2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2ea45aa..41996b9 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "php-http/message-factory": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.0 || ^2.0", "symfony/options-resolver": "~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0 || ^6.0", "symfony/polyfill-php80": "^1.17" }, From 1c90646cea5d88741d764f84f526a7426ae28ec4 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 14 Apr 2023 15:08:52 +0200 Subject: [PATCH 102/123] adjust to new cs rules --- src/PluginChain.php | 4 +--- tests/PluginClientBuilderTest.php | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/PluginChain.php b/src/PluginChain.php index 2dd630c..2bfbfbc 100644 --- a/src/PluginChain.php +++ b/src/PluginChain.php @@ -4,8 +4,6 @@ namespace Http\Client\Common; -use function array_reverse; - use Http\Client\Common\Exception\LoopException; use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; @@ -39,7 +37,7 @@ public function __construct(array $plugins, callable $clientCallable, array $opt private function createChain(): callable { $lastCallable = $this->clientCallable; - $reversedPlugins = array_reverse($this->plugins); + $reversedPlugins = \array_reverse($this->plugins); foreach ($reversedPlugins as $plugin) { $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable) { diff --git a/tests/PluginClientBuilderTest.php b/tests/PluginClientBuilderTest.php index 190c4a5..4a01363 100644 --- a/tests/PluginClientBuilderTest.php +++ b/tests/PluginClientBuilderTest.php @@ -4,7 +4,6 @@ namespace tests\Http\Client\Common; -use Closure; use Http\Client\Common\Plugin; use Http\Client\Common\PluginClient; use Http\Client\Common\PluginClientBuilder; @@ -32,7 +31,7 @@ public function testPriority(string $client): void $client = $this->prophesize($client)->reveal(); $client = $builder->createClient($client); - $closure = Closure::bind( + $closure = \Closure::bind( function (): array { return $this->plugins; }, @@ -58,7 +57,7 @@ public function testOptions(string $client): void $client = $this->prophesize($client)->reveal(); $client = $builder->createClient($client); - $closure = Closure::bind( + $closure = \Closure::bind( function (): array { return $this->options; }, From 3b2affbeffc80eb780431b498c894bf6db0cd09d Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 14 Apr 2023 15:14:30 +0200 Subject: [PATCH 103/123] semantic branch naming --- .github/workflows/static.yml | 2 +- .github/workflows/tests.yml | 2 +- README.md | 4 +--- composer.json | 5 ----- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index fb1e01a..e66f4bf 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -3,7 +3,7 @@ name: static on: push: branches: - - master + - '*.x' pull_request: jobs: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index df61234..75d3d14 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,7 +3,7 @@ name: tests on: push: branches: - - master + - '*.x' pull_request: jobs: diff --git a/README.md b/README.md index 7d13658..48078fa 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ [![Latest Version](https://img.shields.io/github/release/php-http/client-common.svg?style=flat-square)](https://github.com/php-http/client-common/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/client-common/master.svg?style=flat-square)](https://travis-ci.org/php-http/client-common) -[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/client-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/client-common) -[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/client-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/client-common) +[![Build Status](https://github.com/php-http/client-common/actions/workflows/tests.yml/badge.svg)](https://github.com/php-http/client-common/actions/workflows/tests.yml) [![Total Downloads](https://img.shields.io/packagist/dt/php-http/client-common.svg?style=flat-square)](https://packagist.org/packages/php-http/client-common) **Common HTTP Client implementations and tools for HTTPlug.** diff --git a/composer.json b/composer.json index 2ea45aa..79d8ef3 100644 --- a/composer.json +++ b/composer.json @@ -58,10 +58,5 @@ }, "config": { "sort-packages": true - }, - "extra": { - "branch-alias": { - "dev-master": "2.3.x-dev" - } } } From b5ec85701c84dda1db20b4fe9d2523125295e7f0 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 14 Apr 2023 15:17:30 +0200 Subject: [PATCH 104/123] php 8.2, cleanup build matrix --- .github/workflows/.editorconfig | 2 ++ .github/workflows/static.yml | 4 ++-- .github/workflows/tests.yml | 22 ++++++++++++++-------- README.md | 2 ++ composer.json | 2 +- 5 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/.editorconfig diff --git a/.github/workflows/.editorconfig b/.github/workflows/.editorconfig new file mode 100644 index 0000000..7bd3346 --- /dev/null +++ b/.github/workflows/.editorconfig @@ -0,0 +1,2 @@ +[*.yml] +indent_size = 2 diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index e66f4bf..7777dd9 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: PHPStan uses: OskarStark/phpstan-ga@0.12.32 @@ -28,7 +28,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: PHP-CS-Fixer uses: docker://oskarstark/php-cs-fixer-ga diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 75d3d14..807268f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] + php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -42,11 +42,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.1', '7.2', '7.3', '7.4'] + php: ['7.1', '7.4', '8.2'] steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -68,16 +68,22 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - symfony: ['4', '5', '6'] + include: + - symfony: '4' + php-version: '7.1' + - symfony: '5' + php-version: '7.4' + - symfony: '6' + php-version: '8.2' steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 7.1 + php-version: ${{ matrix.php-version }} tools: composer:v2 coverage: none @@ -101,7 +107,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/README.md b/README.md index 48078fa..822d4cf 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ [![Latest Version](https://img.shields.io/github/release/php-http/client-common.svg?style=flat-square)](https://github.com/php-http/client-common/releases) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) [![Build Status](https://github.com/php-http/client-common/actions/workflows/tests.yml/badge.svg)](https://github.com/php-http/client-common/actions/workflows/tests.yml) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/client-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/client-common) +[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/client-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/client-common) [![Total Downloads](https://img.shields.io/packagist/dt/php-http/client-common.svg?style=flat-square)](https://packagist.org/packages/php-http/client-common) **Common HTTP Client implementations and tools for HTTPlug.** diff --git a/composer.json b/composer.json index 79d8ef3..723b239 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "nyholm/psr7": "^1.2", "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", "phpspec/prophecy": "^1.10.2", - "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3" + "phpunit/phpunit": "^7.5.20 || ^8.5.33 || ^9.6.7" }, "suggest": { "ext-json": "To detect JSON responses with the ContentTypePlugin", From 665bfc381bb910385f70391ed3eeefd0b7bbdd0d Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 14 Apr 2023 15:30:08 +0200 Subject: [PATCH 105/123] prepare release --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e795fce..ece4e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 2.6.1 - 2023-04-14 + +- Allow installation with http-message (PSR-7) version 2 in addition to version 1. +- Support for PHP 8.2 + ## 2.6.0 - 2022-09-29 - [RedirectPlugin] Redirection of non GET/HEAD requests with a body now removes the body on follow-up requests, if the From 01856eed0d4b427c0b18522632c1ecff0b3c9d51 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 27 Apr 2023 20:08:33 +0200 Subject: [PATCH 106/123] Remove dependency on php-http/message-factory --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 2fcf4e9..cc5cda8 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,6 @@ "php": "^7.1 || ^8.0", "php-http/httplug": "^2.0", "php-http/message": "^1.6", - "php-http/message-factory": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.0 || ^2.0", From 880509727a447474d2a71b7d7fa5d268ddd3db4b Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 17 May 2023 08:46:59 +0200 Subject: [PATCH 107/123] prepare release --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ece4e78..2c933ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 2.7.0 - 2023-05-17 + +- Dropped `php-http/message-factory` from composer requirements as it is abandoned and this package does not actually use it. + ## 2.6.1 - 2023-04-14 - Allow installation with http-message (PSR-7) version 2 in addition to version 1. From e8e215529fbcaee73769985dca8d911144c75096 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 17 May 2023 08:54:28 +0200 Subject: [PATCH 108/123] fix tests --- composer.json | 3 +- spec/HttpMethodsClientSpec.php | 89 ---------------------------- tests/HttpMethodsClientTest.php | 100 ++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 90 deletions(-) delete mode 100644 spec/HttpMethodsClientSpec.php create mode 100644 tests/HttpMethodsClientTest.php diff --git a/composer.json b/composer.json index cc5cda8..a15838e 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,8 @@ }, "autoload-dev": { "psr-4": { - "spec\\Http\\Client\\Common\\": "spec/" + "spec\\Http\\Client\\Common\\": "spec/", + "Tests\\Http\\Client\\Common\\": "tests/" } }, "scripts": { diff --git a/spec/HttpMethodsClientSpec.php b/spec/HttpMethodsClientSpec.php deleted file mode 100644 index 68e124d..0000000 --- a/spec/HttpMethodsClientSpec.php +++ /dev/null @@ -1,89 +0,0 @@ - '/uri', - 'headers' => [ - 'Content-Type' => 'text/plain', - ], - 'body' => 'body', - ]; - - public function let(HttpClient $client, RequestFactory $requestFactory) - { - $this->beAnInstanceOf( - HttpMethodsClient::class, [ - $client, - $requestFactory, - ] - ); - } - - public function it_sends_a_get_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) - { - $this->assert($client, $requestFactory, $request, $response, 'get'); - } - - public function it_sends_a_head_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) - { - $this->assert($client, $requestFactory, $request, $response, 'head'); - } - - public function it_sends_a_trace_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) - { - $this->assert($client, $requestFactory, $request, $response, 'trace'); - } - - public function it_sends_a_post_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) - { - $this->assert($client, $requestFactory, $request, $response, 'post', self::$requestData['body']); - } - - public function it_sends_a_put_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) - { - $this->assert($client, $requestFactory, $request, $response, 'put', self::$requestData['body']); - } - - public function it_sends_a_patch_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) - { - $this->assert($client, $requestFactory, $request, $response, 'patch', self::$requestData['body']); - } - - public function it_sends_a_delete_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) - { - $this->assert($client, $requestFactory, $request, $response, 'delete', self::$requestData['body']); - } - - public function it_sends_an_options_request(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response) - { - $this->assert($client, $requestFactory, $request, $response, 'options', self::$requestData['body']); - } - - /** - * Run the actual test. - * - * As there is no data provider in phpspec, we keep separate methods to get new mocks for each test. - */ - private function assert(HttpClient $client, RequestFactory $requestFactory, RequestInterface $request, ResponseInterface $response, string $method, string $body = null) - { - $client->sendRequest($request)->shouldBeCalled()->willReturn($response); - $this->mockFactory($requestFactory, $request, strtoupper($method), $body); - - $this->$method(self::$requestData['uri'], self::$requestData['headers'], self::$requestData['body'])->shouldReturnAnInstanceOf(ResponseInterface::class); - } - - private function mockFactory(RequestFactory $requestFactory, RequestInterface $request, string $method, string $body = null) - { - $requestFactory->createRequest($method, self::$requestData['uri'], self::$requestData['headers'], $body)->willReturn($request); - } -} diff --git a/tests/HttpMethodsClientTest.php b/tests/HttpMethodsClientTest.php new file mode 100644 index 0000000..4ec9f69 --- /dev/null +++ b/tests/HttpMethodsClientTest.php @@ -0,0 +1,100 @@ +httpClient = $this->createMock(ClientInterface::class); + $streamFactory = $requestFactory = new Psr17Factory(); + $this->httpMethodsClient = new HttpMethodsClient($this->httpClient, $requestFactory, $streamFactory); + } + + public function testGet(): void + { + $this->expectSendRequest('get'); + } + + public function testHead(): void + { + $this->expectSendRequest('head'); + } + + public function testTrace(): void + { + $this->expectSendRequest('trace'); + } + + public function testPost(): void + { + $this->expectSendRequest('post', self::BODY); + } + + public function testPut(): void + { + $this->expectSendRequest('put', self::BODY); + } + + public function testPatch(): void + { + $this->expectSendRequest('patch', self::BODY); + } + + public function testDelete(): void + { + $this->expectSendRequest('delete', self::BODY); + } + + public function testOptions(): void + { + $this->expectSendRequest('options', self::BODY); + } + + /** + * Run the actual test. + * + * As there is no data provider in phpspec, we keep separate methods to get new mocks for each test. + */ + private function expectSendRequest(string $method, string $body = null): void + { + $response = new Response(); + $this->httpClient->expects($this->once()) + ->method('sendRequest') + ->with(self::callback(static function (RequestInterface $request) use ($body, $method): bool { + self::assertSame(strtoupper($method), $request->getMethod()); + self::assertSame(self::URI, (string) $request->getUri()); + self::assertSame([self::HEADER_NAME => [self::HEADER_VALUE]], $request->getHeaders()); + self::assertSame((string) $body, (string) $request->getBody()); + + return true; + })) + ->willReturn($response) + ; + + $actualResponse = $this->httpMethodsClient->$method(self::URI, [self::HEADER_NAME => self::HEADER_VALUE], self::BODY); + $this->assertSame($response, $actualResponse); + } +} From 1467264599ae6dc47bd1cce0466bc7f57e79624e Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 17 May 2023 10:04:23 +0200 Subject: [PATCH 109/123] add factory stub for phpstan --- phpstan.neon.dist | 21 +++++++++++++++++++++ tests/Plugin/AddPathPluginTest.php | 2 +- tests/Plugin/RedirectPluginTest.php | 2 +- tests/PluginChainTest.php | 2 +- tests/PluginClientBuilderTest.php | 2 +- tests/PluginClientTest.php | 2 +- 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 236087f..8886dea 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -31,6 +31,27 @@ parameters: count: 1 path: src/EmulatedHttpClient.php + # we still support the obsolete RequestFactory for BC but do not require the package anymore + - + message: "#^Call to method createRequest\\(\\) on an unknown class Http\\\\Message\\\\RequestFactory\\.$#" + count: 1 + path: src/HttpMethodsClient.php + + - + message: "#^Class Http\\\\Message\\\\RequestFactory not found\\.$#" + count: 4 + path: src/HttpMethodsClient.php + + - + message: "#^Parameter \\$requestFactory of method Http\\\\Client\\\\Common\\\\HttpMethodsClient\\:\\:__construct\\(\\) has invalid type Http\\\\Message\\\\RequestFactory\\.$#" + count: 1 + path: src/HttpMethodsClient.php + + - + message: "#^Property Http\\\\Client\\\\Common\\\\HttpMethodsClient\\:\\:\\$requestFactory has unknown class Http\\\\Message\\\\RequestFactory as its type\\.$#" + count: 1 + path: src/HttpMethodsClient.php + - message: "#^Anonymous function should return Psr\\\\Http\\\\Message\\\\ResponseInterface but returns mixed\\.$#" count: 1 diff --git a/tests/Plugin/AddPathPluginTest.php b/tests/Plugin/AddPathPluginTest.php index 55081a8..9457535 100644 --- a/tests/Plugin/AddPathPluginTest.php +++ b/tests/Plugin/AddPathPluginTest.php @@ -1,6 +1,6 @@ Date: Thu, 2 Nov 2023 17:08:33 +0100 Subject: [PATCH 110/123] Allow using Symfony 7 dependencies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a15838e..25afb92 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.0 || ^2.0", - "symfony/options-resolver": "~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0 || ^6.0", + "symfony/options-resolver": "~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0 || ^6.0 || ^7.0", "symfony/polyfill-php80": "^1.17" }, "require-dev": { From 1e19c059b0e4d5f717bf5d524d616165aeab0612 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 30 Nov 2023 11:31:25 +0100 Subject: [PATCH 111/123] prepare release --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c933ab..f4b989a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 2.7.1 - 2023-11-30 + +- Allow installation with Symfony 7. + ## 2.7.0 - 2023-05-17 - Dropped `php-http/message-factory` from composer requirements as it is abandoned and this package does not actually use it. From ce4e4093d124b3b5a80ee5ffce2d78aa5e3bf149 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 3 Nov 2023 16:42:32 +0100 Subject: [PATCH 112/123] update with latest cs fixer --- .github/workflows/static.yml | 3 +++ CHANGELOG.md | 4 ++++ src/Deferred.php | 9 --------- src/HttpAsyncClientDecorator.php | 2 -- src/HttpAsyncClientEmulator.php | 4 ---- src/HttpClientDecorator.php | 2 -- src/HttpClientEmulator.php | 4 ---- src/HttpClientPool/HttpClientPool.php | 6 ------ src/HttpClientPool/HttpClientPoolItem.php | 6 ------ src/HttpClientPool/LeastUsedClientPool.php | 3 --- src/HttpClientPool/RandomClientPool.php | 3 --- src/HttpClientPool/RoundRobinClientPool.php | 3 --- src/HttpClientRouter.php | 8 -------- src/Plugin/AddHostPlugin.php | 3 --- src/Plugin/AuthenticationPlugin.php | 3 --- src/Plugin/BaseUriPlugin.php | 5 +---- src/Plugin/ContentLengthPlugin.php | 3 --- src/Plugin/ContentTypePlugin.php | 3 --- src/Plugin/CookiePlugin.php | 3 --- src/Plugin/DecoderPlugin.php | 3 --- src/Plugin/ErrorPlugin.php | 3 --- src/Plugin/HeaderAppendPlugin.php | 3 --- src/Plugin/HeaderDefaultsPlugin.php | 3 --- src/Plugin/HeaderRemovePlugin.php | 3 --- src/Plugin/HeaderSetPlugin.php | 3 --- src/Plugin/HistoryPlugin.php | 3 --- src/Plugin/QueryDefaultsPlugin.php | 3 --- src/Plugin/RedirectPlugin.php | 3 --- src/Plugin/RequestMatcherPlugin.php | 3 --- src/Plugin/RequestSeekableBodyPlugin.php | 3 --- src/Plugin/ResponseSeekableBodyPlugin.php | 3 --- src/Plugin/RetryPlugin.php | 3 --- src/PluginClient.php | 6 ------ src/PluginClientBuilder.php | 2 +- 34 files changed, 9 insertions(+), 115 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 7777dd9..b75f77c 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -15,6 +15,9 @@ jobs: - name: Checkout code uses: actions/checkout@v3 + - name: Remove phpspec + run: composer remove --dev friends-of-phpspec/phpspec-code-coverage phpspec/phpspec + - name: PHPStan uses: OskarStark/phpstan-ga@0.12.32 env: diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b989a..4cfd5c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## unreleased + +- Cleaned up phpdoc. + ## 2.7.1 - 2023-11-30 - Allow installation with Symfony 7. diff --git a/src/Deferred.php b/src/Deferred.php index 646f375..eaf7cdf 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -51,9 +51,6 @@ public function __construct(callable $waitCallback) $this->onRejectedCallbacks = []; } - /** - * {@inheritdoc} - */ public function then(callable $onFulfilled = null, callable $onRejected = null): Promise { $deferred = new self($this->waitCallback); @@ -86,9 +83,6 @@ public function then(callable $onFulfilled = null, callable $onRejected = null): return $deferred; } - /** - * {@inheritdoc} - */ public function getState(): string { return $this->state; @@ -128,9 +122,6 @@ public function reject(ClientExceptionInterface $exception): void } } - /** - * {@inheritdoc} - */ public function wait($unwrap = true) { if (Promise::PENDING === $this->state) { diff --git a/src/HttpAsyncClientDecorator.php b/src/HttpAsyncClientDecorator.php index 2714b4a..91ff5af 100644 --- a/src/HttpAsyncClientDecorator.php +++ b/src/HttpAsyncClientDecorator.php @@ -20,8 +20,6 @@ trait HttpAsyncClientDecorator protected $httpAsyncClient; /** - * {@inheritdoc} - * * @see HttpAsyncClient::sendAsyncRequest */ public function sendAsyncRequest(RequestInterface $request) diff --git a/src/HttpAsyncClientEmulator.php b/src/HttpAsyncClientEmulator.php index 53c2535..ffc0d5a 100644 --- a/src/HttpAsyncClientEmulator.php +++ b/src/HttpAsyncClientEmulator.php @@ -17,15 +17,11 @@ trait HttpAsyncClientEmulator { /** - * {@inheritdoc} - * * @see HttpClient::sendRequest */ abstract public function sendRequest(RequestInterface $request): ResponseInterface; /** - * {@inheritdoc} - * * @see HttpAsyncClient::sendAsyncRequest */ public function sendAsyncRequest(RequestInterface $request) diff --git a/src/HttpClientDecorator.php b/src/HttpClientDecorator.php index c00ba6f..d236ad5 100644 --- a/src/HttpClientDecorator.php +++ b/src/HttpClientDecorator.php @@ -21,8 +21,6 @@ trait HttpClientDecorator protected $httpClient; /** - * {@inheritdoc} - * * @see ClientInterface::sendRequest */ public function sendRequest(RequestInterface $request): ResponseInterface diff --git a/src/HttpClientEmulator.php b/src/HttpClientEmulator.php index 51e2c05..7c40b50 100644 --- a/src/HttpClientEmulator.php +++ b/src/HttpClientEmulator.php @@ -15,8 +15,6 @@ trait HttpClientEmulator { /** - * {@inheritdoc} - * * @see HttpClient::sendRequest */ public function sendRequest(RequestInterface $request): ResponseInterface @@ -27,8 +25,6 @@ public function sendRequest(RequestInterface $request): ResponseInterface } /** - * {@inheritdoc} - * * @see HttpAsyncClient::sendAsyncRequest */ abstract public function sendAsyncRequest(RequestInterface $request); diff --git a/src/HttpClientPool/HttpClientPool.php b/src/HttpClientPool/HttpClientPool.php index a30bdb0..d7c2dbd 100644 --- a/src/HttpClientPool/HttpClientPool.php +++ b/src/HttpClientPool/HttpClientPool.php @@ -52,17 +52,11 @@ public function addHttpClient($client): void */ abstract protected function chooseHttpClient(): HttpClientPoolItem; - /** - * {@inheritdoc} - */ public function sendAsyncRequest(RequestInterface $request) { return $this->chooseHttpClient()->sendAsyncRequest($request); } - /** - * {@inheritdoc} - */ public function sendRequest(RequestInterface $request): ResponseInterface { return $this->chooseHttpClient()->sendRequest($request); diff --git a/src/HttpClientPool/HttpClientPoolItem.php b/src/HttpClientPool/HttpClientPoolItem.php index f29d065..2496dda 100644 --- a/src/HttpClientPool/HttpClientPoolItem.php +++ b/src/HttpClientPool/HttpClientPoolItem.php @@ -69,9 +69,6 @@ public function __construct($client, int $reenableAfter = null) $this->reenableAfter = $reenableAfter; } - /** - * {@inheritdoc} - */ public function sendRequest(RequestInterface $request): ResponseInterface { if ($this->isDisabled()) { @@ -92,9 +89,6 @@ public function sendRequest(RequestInterface $request): ResponseInterface return $response; } - /** - * {@inheritdoc} - */ public function sendAsyncRequest(RequestInterface $request) { if ($this->isDisabled()) { diff --git a/src/HttpClientPool/LeastUsedClientPool.php b/src/HttpClientPool/LeastUsedClientPool.php index 789c357..bfa1cd0 100644 --- a/src/HttpClientPool/LeastUsedClientPool.php +++ b/src/HttpClientPool/LeastUsedClientPool.php @@ -15,9 +15,6 @@ */ final class LeastUsedClientPool extends HttpClientPool { - /** - * {@inheritdoc} - */ protected function chooseHttpClient(): HttpClientPoolItem { $clientPool = array_filter($this->clientPool, function (HttpClientPoolItem $clientPoolItem) { diff --git a/src/HttpClientPool/RandomClientPool.php b/src/HttpClientPool/RandomClientPool.php index 789ba42..8fda587 100644 --- a/src/HttpClientPool/RandomClientPool.php +++ b/src/HttpClientPool/RandomClientPool.php @@ -13,9 +13,6 @@ */ final class RandomClientPool extends HttpClientPool { - /** - * {@inheritdoc} - */ protected function chooseHttpClient(): HttpClientPoolItem { $clientPool = array_filter($this->clientPool, function (HttpClientPoolItem $clientPoolItem) { diff --git a/src/HttpClientPool/RoundRobinClientPool.php b/src/HttpClientPool/RoundRobinClientPool.php index 7c7b191..a908653 100644 --- a/src/HttpClientPool/RoundRobinClientPool.php +++ b/src/HttpClientPool/RoundRobinClientPool.php @@ -13,9 +13,6 @@ */ final class RoundRobinClientPool extends HttpClientPool { - /** - * {@inheritdoc} - */ protected function chooseHttpClient(): HttpClientPoolItem { $last = current($this->clientPool); diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index 040d893..42c6907 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -12,8 +12,6 @@ use Psr\Http\Message\ResponseInterface; /** - * {@inheritdoc} - * * @author Joel Wurtz */ final class HttpClientRouter implements HttpClientRouterInterface @@ -23,17 +21,11 @@ final class HttpClientRouter implements HttpClientRouterInterface */ private $clients = []; - /** - * {@inheritdoc} - */ public function sendRequest(RequestInterface $request): ResponseInterface { return $this->chooseHttpClient($request)->sendRequest($request); } - /** - * {@inheritdoc} - */ public function sendAsyncRequest(RequestInterface $request) { return $this->chooseHttpClient($request)->sendAsyncRequest($request); diff --git a/src/Plugin/AddHostPlugin.php b/src/Plugin/AddHostPlugin.php index 2a866ee..ca811b7 100644 --- a/src/Plugin/AddHostPlugin.php +++ b/src/Plugin/AddHostPlugin.php @@ -48,9 +48,6 @@ public function __construct(UriInterface $host, array $config = []) $this->replace = $options['replace']; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { if ($this->replace || '' === $request->getUri()->getHost()) { diff --git a/src/Plugin/AuthenticationPlugin.php b/src/Plugin/AuthenticationPlugin.php index ce9d4bd..2336062 100644 --- a/src/Plugin/AuthenticationPlugin.php +++ b/src/Plugin/AuthenticationPlugin.php @@ -26,9 +26,6 @@ public function __construct(Authentication $authentication) $this->authentication = $authentication; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $request = $this->authentication->authenticate($request); diff --git a/src/Plugin/BaseUriPlugin.php b/src/Plugin/BaseUriPlugin.php index 34c3b64..c361683 100644 --- a/src/Plugin/BaseUriPlugin.php +++ b/src/Plugin/BaseUriPlugin.php @@ -24,7 +24,7 @@ final class BaseUriPlugin implements Plugin /** * @var AddPathPlugin|null */ - private $addPathPlugin = null; + private $addPathPlugin; /** * @param UriInterface $uri Has to contain a host name and can have a path @@ -39,9 +39,6 @@ public function __construct(UriInterface $uri, array $hostConfig = []) } } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $addHostNext = function (RequestInterface $request) use ($next, $first) { diff --git a/src/Plugin/ContentLengthPlugin.php b/src/Plugin/ContentLengthPlugin.php index f313c33..ad69ff3 100644 --- a/src/Plugin/ContentLengthPlugin.php +++ b/src/Plugin/ContentLengthPlugin.php @@ -16,9 +16,6 @@ */ final class ContentLengthPlugin implements Plugin { - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { if (!$request->hasHeader('Content-Length')) { diff --git a/src/Plugin/ContentTypePlugin.php b/src/Plugin/ContentTypePlugin.php index da3758e..e4844c4 100644 --- a/src/Plugin/ContentTypePlugin.php +++ b/src/Plugin/ContentTypePlugin.php @@ -57,9 +57,6 @@ public function __construct(array $config = []) $this->sizeLimit = $options['size_limit']; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { if (!$request->hasHeader('Content-Type')) { diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index aa4d5d7..6a8942a 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -33,9 +33,6 @@ public function __construct(CookieJar $cookieJar) $this->cookieJar = $cookieJar; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $cookies = []; diff --git a/src/Plugin/DecoderPlugin.php b/src/Plugin/DecoderPlugin.php index 41c1a58..3e781aa 100644 --- a/src/Plugin/DecoderPlugin.php +++ b/src/Plugin/DecoderPlugin.php @@ -48,9 +48,6 @@ public function __construct(array $config = []) $this->useContentEncoding = $options['use_content_encoding']; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $encodings = extension_loaded('zlib') ? ['gzip', 'deflate'] : ['identity']; diff --git a/src/Plugin/ErrorPlugin.php b/src/Plugin/ErrorPlugin.php index 678977f..712e28a 100644 --- a/src/Plugin/ErrorPlugin.php +++ b/src/Plugin/ErrorPlugin.php @@ -54,9 +54,6 @@ public function __construct(array $config = []) $this->onlyServerException = $options['only_server_exception']; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $promise = $next($request); diff --git a/src/Plugin/HeaderAppendPlugin.php b/src/Plugin/HeaderAppendPlugin.php index 95ea673..e6d6235 100644 --- a/src/Plugin/HeaderAppendPlugin.php +++ b/src/Plugin/HeaderAppendPlugin.php @@ -34,9 +34,6 @@ public function __construct(array $headers) $this->headers = $headers; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { foreach ($this->headers as $header => $headerValue) { diff --git a/src/Plugin/HeaderDefaultsPlugin.php b/src/Plugin/HeaderDefaultsPlugin.php index bf58070..baf8f9d 100644 --- a/src/Plugin/HeaderDefaultsPlugin.php +++ b/src/Plugin/HeaderDefaultsPlugin.php @@ -30,9 +30,6 @@ public function __construct(array $headers) $this->headers = $headers; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { foreach ($this->headers as $header => $headerValue) { diff --git a/src/Plugin/HeaderRemovePlugin.php b/src/Plugin/HeaderRemovePlugin.php index 9f4ca44..abfb922 100644 --- a/src/Plugin/HeaderRemovePlugin.php +++ b/src/Plugin/HeaderRemovePlugin.php @@ -28,9 +28,6 @@ public function __construct(array $headers) $this->headers = $headers; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { foreach ($this->headers as $header) { diff --git a/src/Plugin/HeaderSetPlugin.php b/src/Plugin/HeaderSetPlugin.php index 06f00eb..1ca9fb3 100644 --- a/src/Plugin/HeaderSetPlugin.php +++ b/src/Plugin/HeaderSetPlugin.php @@ -30,9 +30,6 @@ public function __construct(array $headers) $this->headers = $headers; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { foreach ($this->headers as $header => $headerValue) { diff --git a/src/Plugin/HistoryPlugin.php b/src/Plugin/HistoryPlugin.php index a1796a6..15597ee 100644 --- a/src/Plugin/HistoryPlugin.php +++ b/src/Plugin/HistoryPlugin.php @@ -29,9 +29,6 @@ public function __construct(Journal $journal) $this->journal = $journal; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $journal = $this->journal; diff --git a/src/Plugin/QueryDefaultsPlugin.php b/src/Plugin/QueryDefaultsPlugin.php index 4c8087c..fc11ba1 100644 --- a/src/Plugin/QueryDefaultsPlugin.php +++ b/src/Plugin/QueryDefaultsPlugin.php @@ -31,9 +31,6 @@ public function __construct(array $queryParams) $this->queryParams = $queryParams; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $uri = $request->getUri(); diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index ee5c232..beb633e 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -158,9 +158,6 @@ public function __construct(array $config = []) $this->streamFactory = $options['stream_factory']; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { // Check in storage diff --git a/src/Plugin/RequestMatcherPlugin.php b/src/Plugin/RequestMatcherPlugin.php index 45d4375..8373409 100644 --- a/src/Plugin/RequestMatcherPlugin.php +++ b/src/Plugin/RequestMatcherPlugin.php @@ -38,9 +38,6 @@ public function __construct(RequestMatcher $requestMatcher, ?Plugin $delegateOnM $this->failurePlugin = $delegateOnNoMatch; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { if ($this->requestMatcher->matches($request)) { diff --git a/src/Plugin/RequestSeekableBodyPlugin.php b/src/Plugin/RequestSeekableBodyPlugin.php index 1b6c528..f39c88d 100644 --- a/src/Plugin/RequestSeekableBodyPlugin.php +++ b/src/Plugin/RequestSeekableBodyPlugin.php @@ -15,9 +15,6 @@ */ final class RequestSeekableBodyPlugin extends SeekableBodyPlugin { - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { if (!$request->getBody()->isSeekable()) { diff --git a/src/Plugin/ResponseSeekableBodyPlugin.php b/src/Plugin/ResponseSeekableBodyPlugin.php index 6f941b6..ef8bee4 100644 --- a/src/Plugin/ResponseSeekableBodyPlugin.php +++ b/src/Plugin/ResponseSeekableBodyPlugin.php @@ -16,9 +16,6 @@ */ final class ResponseSeekableBodyPlugin extends SeekableBodyPlugin { - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { return $next($request)->then(function (ResponseInterface $response) { diff --git a/src/Plugin/RetryPlugin.php b/src/Plugin/RetryPlugin.php index 144679e..992bb21 100644 --- a/src/Plugin/RetryPlugin.php +++ b/src/Plugin/RetryPlugin.php @@ -96,9 +96,6 @@ public function __construct(array $config = []) $this->exceptionDelay = $options['exception_delay']; } - /** - * {@inheritdoc} - */ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $chainIdentifier = spl_object_hash((object) $first); diff --git a/src/PluginClient.php b/src/PluginClient.php index db81243..d728c78 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -64,9 +64,6 @@ public function __construct($client, array $plugins = [], array $options = []) $this->options = $this->configure($options); } - /** - * {@inheritdoc} - */ public function sendRequest(RequestInterface $request): ResponseInterface { // If the client doesn't support sync calls, call async @@ -87,9 +84,6 @@ public function sendRequest(RequestInterface $request): ResponseInterface return $pluginChain($request)->wait(); } - /** - * {@inheritdoc} - */ public function sendAsyncRequest(RequestInterface $request) { $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) { diff --git a/src/PluginClientBuilder.php b/src/PluginClientBuilder.php index 8746498..45fd787 100644 --- a/src/PluginClientBuilder.php +++ b/src/PluginClientBuilder.php @@ -31,7 +31,7 @@ public function addPlugin(Plugin $plugin, int $priority = 0): self } /** - * @param mixed $value + * @param string|int|float|bool|string[] $value */ public function setOption(string $name, $value): self { From e93af132d0a41deb29d93bbc73e78af5304d06ed Mon Sep 17 00:00:00 2001 From: Quentin Dreyer Date: Sat, 13 Jan 2024 11:07:32 +0100 Subject: [PATCH 113/123] chore: fix random client pool phpdoc --- src/HttpClientPool/RandomClientPool.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HttpClientPool/RandomClientPool.php b/src/HttpClientPool/RandomClientPool.php index 8fda587..8ef535f 100644 --- a/src/HttpClientPool/RandomClientPool.php +++ b/src/HttpClientPool/RandomClientPool.php @@ -7,7 +7,7 @@ use Http\Client\Common\Exception\HttpClientNotFoundException; /** - * RoundRobinClientPool will choose the next client in the pool. + * RandomClientPool will choose a random enabled client in the pool. * * @author Joel Wurtz */ From bd6df908ce5335dbdfa6afdc40636c7a35cbdd88 Mon Sep 17 00:00:00 2001 From: Christopher Georg Date: Sun, 21 Jan 2024 11:46:52 +0100 Subject: [PATCH 114/123] test: fix namespace for RedirectPluginTest --- tests/Plugin/RedirectPluginTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Plugin/RedirectPluginTest.php b/tests/Plugin/RedirectPluginTest.php index 4c95bba..7fbf6f0 100644 --- a/tests/Plugin/RedirectPluginTest.php +++ b/tests/Plugin/RedirectPluginTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tests\Http\Cient\Common\Plugin; +namespace Tests\Http\Client\Common\Plugin; use Http\Client\Common\Exception\CircularRedirectionException; use Http\Client\Common\Plugin\RedirectPlugin; From 1c5944587cd50438c84131c8298ffccb38dfb6fa Mon Sep 17 00:00:00 2001 From: Christopher Georg Date: Sun, 21 Jan 2024 11:56:01 +0100 Subject: [PATCH 115/123] chore: add testruns for php 8.3 bump github actions "actions/checkout" version 3 => 4 --- .github/workflows/static.yml | 4 ++-- .github/workflows/tests.yml | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index b75f77c..08d51d6 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Remove phpspec run: composer remove --dev friends-of-phpspec/phpspec-code-coverage phpspec/phpspec @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: PHP-CS-Fixer uses: docker://oskarstark/php-cs-fixer-ga diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 807268f..8748627 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -42,11 +42,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.1', '7.4', '8.2'] + php: ['7.1', '7.4', '8.2', '8.3'] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -78,7 +78,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -107,7 +107,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 From 78319680cc02916e0430e0b9d5d3b5ff420d0640 Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 23 Jan 2024 08:22:24 +0100 Subject: [PATCH 116/123] chore: bump github actions "OskarStark/phpstan-ga" version 0.x => 1.13.0 (#241) * chore: bump github action "OskarStark/phpstan-ga" --------- Co-authored-by: Christopher Georg --- .github/workflows/static.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 08d51d6..7585030 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -19,7 +19,7 @@ jobs: run: composer remove --dev friends-of-phpspec/phpspec-code-coverage phpspec/phpspec - name: PHPStan - uses: OskarStark/phpstan-ga@0.12.32 + uses: docker://oskarstark/phpstan-ga env: REQUIRE_DEV: false with: From f61eca22a58133bdc4b482d470af6aaa7d7e92d9 Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 23 Jan 2024 08:26:33 +0100 Subject: [PATCH 117/123] chore: add testrun for symfony 7 (#237) * add testrun for symfony 7 * fix symfony flex setup --------- Co-authored-by: Christopher Georg --- .github/workflows/tests.yml | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8748627..5c51567 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,12 +25,6 @@ jobs: tools: composer:v2 coverage: none - - name: Require PHPSpec 7.1 dependencies - run: | - composer require "phpspec/phpspec:^7.1@dev" --no-interaction --no-update - composer update --prefer-dist --no-interaction --no-progress --ignore-platform-req=php - if: "matrix.php == '8.1'" - - name: Install PHP dependencies run: composer update --prefer-dist --no-interaction --no-progress @@ -56,9 +50,7 @@ jobs: coverage: none - name: Install dependencies - run: | - composer require "sebastian/comparator:^3.0.2" --no-interaction --no-update - composer update --prefer-dist --prefer-stable --prefer-lowest --no-interaction --no-progress + run: composer update --prefer-dist --prefer-stable --prefer-lowest --no-interaction --no-progress - name: Execute tests run: composer test @@ -69,11 +61,13 @@ jobs: strategy: matrix: include: - - symfony: '4' + - symfony: '4.4.*' php-version: '7.1' - - symfony: '5' + - symfony: '5.4.*' php-version: '7.4' - - symfony: '6' + - symfony: '6.4.*' + php-version: '8.2' + - symfony: '7.0.*' php-version: '8.2' steps: @@ -87,16 +81,13 @@ jobs: tools: composer:v2 coverage: none - - name: Pin old packages - run: composer require "phpspec/phpspec:^2.5.8" --no-interaction --no-update - if: "matrix.symfony == '2'" - - name: Install dependencies + env: + SYMFONY_REQUIRE: ${{ matrix.symfony }} run: | - composer config --no-plugins allow-plugins.symfony/flex true - composer require --no-update --no-interaction --no-progress symfony/flex - composer config extra.symfony.require ${{ matrix.symfony}} - composer update --prefer-dist --no-interaction --prefer-stable --prefer-lowest --no-progress + composer global config --no-plugins allow-plugins.symfony/flex true + composer global require --no-progress --no-scripts --no-plugins symfony/flex + composer update --prefer-dist --no-interaction --prefer-stable --no-progress - name: Execute tests run: composer test From 28ac1395085ef69c5c9d357909654ee8e40b5cbd Mon Sep 17 00:00:00 2001 From: Christopher Georg Date: Fri, 26 Jan 2024 20:29:49 +0100 Subject: [PATCH 118/123] chore: fix phpDoc for return values --- src/Plugin/CookiePlugin.php | 2 +- src/Plugin/RedirectPlugin.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index 6a8942a..b120ecf 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -164,7 +164,7 @@ private function createCookie(RequestInterface $request, string $setCookieHeader * * @param string $part A single cookie value in format key=value * - * @return array{0:string, 1:?string} + * @return array{0:string, 1?:string} */ private function createValueKey(string $part): array { diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index beb633e..3ba71d6 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -113,7 +113,7 @@ final class RedirectPlugin implements Plugin private $streamFactory; /** - * @param array{'preserve_header'?: bool|string[], 'use_default_for_multiple'?: bool, 'strict'?: bool} $config + * @param array{'preserve_header'?: bool|string[], 'use_default_for_multiple'?: bool, 'strict'?: bool, 'stream_factory'?:StreamFactoryInterface} $config * * Configuration options: * - preserve_header: True keeps all headers, false remove all of them, an array is interpreted as a list of header names to keep From d34b90cecdcefd191379331649f980cddcf3f297 Mon Sep 17 00:00:00 2001 From: Christopher Georg Date: Fri, 26 Jan 2024 20:34:27 +0100 Subject: [PATCH 119/123] chore: fix phpDoc for return values --- src/Plugin/CookiePlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index b120ecf..6a8942a 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -164,7 +164,7 @@ private function createCookie(RequestInterface $request, string $setCookieHeader * * @param string $part A single cookie value in format key=value * - * @return array{0:string, 1?:string} + * @return array{0:string, 1:?string} */ private function createValueKey(string $part): array { From dd6c78140aee05dff390c32c7fbba393f7682d10 Mon Sep 17 00:00:00 2001 From: Christopher Georg Date: Fri, 26 Jan 2024 20:37:31 +0100 Subject: [PATCH 120/123] chore: fix phpDoc for return values --- src/Plugin/CookiePlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index 6a8942a..23c8593 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -164,7 +164,7 @@ private function createCookie(RequestInterface $request, string $setCookieHeader * * @param string $part A single cookie value in format key=value * - * @return array{0:string, 1:?string} + * @return array{0:string, 1:string|null} */ private function createValueKey(string $part): array { From b25792f474beb9de22801e4ef255e4f0b02479d2 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Mon, 23 Sep 2024 13:14:36 +0200 Subject: [PATCH 121/123] adjust to latest cs fixer --- src/Deferred.php | 2 +- src/Exception/HttpClientNoMatchException.php | 2 +- src/HttpClientPool/HttpClientPoolItem.php | 2 +- src/HttpMethodsClient.php | 2 +- src/Plugin/RedirectPlugin.php | 2 +- src/Plugin/RequestMatcherPlugin.php | 2 +- tests/HttpMethodsClientTest.php | 2 +- tests/Plugin/RedirectPluginTest.php | 2 +- tests/PluginClientTest.php | 6 +++--- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Deferred.php b/src/Deferred.php index eaf7cdf..effabb2 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -51,7 +51,7 @@ public function __construct(callable $waitCallback) $this->onRejectedCallbacks = []; } - public function then(callable $onFulfilled = null, callable $onRejected = null): Promise + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): Promise { $deferred = new self($this->waitCallback); diff --git a/src/Exception/HttpClientNoMatchException.php b/src/Exception/HttpClientNoMatchException.php index 682c5dd..83037d3 100644 --- a/src/Exception/HttpClientNoMatchException.php +++ b/src/Exception/HttpClientNoMatchException.php @@ -19,7 +19,7 @@ final class HttpClientNoMatchException extends TransferException */ private $request; - public function __construct(string $message, RequestInterface $request, \Exception $previous = null) + public function __construct(string $message, RequestInterface $request, ?\Exception $previous = null) { $this->request = $request; diff --git a/src/HttpClientPool/HttpClientPoolItem.php b/src/HttpClientPool/HttpClientPoolItem.php index 2496dda..1bf4381 100644 --- a/src/HttpClientPool/HttpClientPoolItem.php +++ b/src/HttpClientPool/HttpClientPoolItem.php @@ -57,7 +57,7 @@ class HttpClientPoolItem implements HttpClient, HttpAsyncClient * @param ClientInterface|HttpAsyncClient $client * @param int|null $reenableAfter Number of seconds until this client is enabled again after an error */ - public function __construct($client, int $reenableAfter = null) + public function __construct($client, ?int $reenableAfter = null) { if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) { throw new \TypeError( diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index 497921b..95fee3b 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -33,7 +33,7 @@ final class HttpMethodsClient implements HttpMethodsClientInterface /** * @param RequestFactory|RequestFactoryInterface $requestFactory */ - public function __construct(ClientInterface $httpClient, $requestFactory, StreamFactoryInterface $streamFactory = null) + public function __construct(ClientInterface $httpClient, $requestFactory, ?StreamFactoryInterface $streamFactory = null) { if (!$requestFactory instanceof RequestFactory && !$requestFactory instanceof RequestFactoryInterface) { throw new \TypeError( diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index 3ba71d6..8aebcbf 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -218,7 +218,7 @@ public function guessStreamFactory(): ?StreamFactoryInterface return new Psr17Factory(); } if (class_exists(Utils::class)) { - return new class() implements StreamFactoryInterface { + return new class implements StreamFactoryInterface { public function createStream(string $content = ''): StreamInterface { return Utils::streamFor($content); diff --git a/src/Plugin/RequestMatcherPlugin.php b/src/Plugin/RequestMatcherPlugin.php index 8373409..eb97d92 100644 --- a/src/Plugin/RequestMatcherPlugin.php +++ b/src/Plugin/RequestMatcherPlugin.php @@ -31,7 +31,7 @@ final class RequestMatcherPlugin implements Plugin */ private $failurePlugin; - public function __construct(RequestMatcher $requestMatcher, ?Plugin $delegateOnMatch, Plugin $delegateOnNoMatch = null) + public function __construct(RequestMatcher $requestMatcher, ?Plugin $delegateOnMatch, ?Plugin $delegateOnNoMatch = null) { $this->requestMatcher = $requestMatcher; $this->successPlugin = $delegateOnMatch; diff --git a/tests/HttpMethodsClientTest.php b/tests/HttpMethodsClientTest.php index 4ec9f69..d93a2a8 100644 --- a/tests/HttpMethodsClientTest.php +++ b/tests/HttpMethodsClientTest.php @@ -78,7 +78,7 @@ public function testOptions(): void * * As there is no data provider in phpspec, we keep separate methods to get new mocks for each test. */ - private function expectSendRequest(string $method, string $body = null): void + private function expectSendRequest(string $method, ?string $body = null): void { $response = new Response(); $this->httpClient->expects($this->once()) diff --git a/tests/Plugin/RedirectPluginTest.php b/tests/Plugin/RedirectPluginTest.php index 7fbf6f0..92072ad 100644 --- a/tests/Plugin/RedirectPluginTest.php +++ b/tests/Plugin/RedirectPluginTest.php @@ -93,7 +93,7 @@ public function provideRedirections(): array 'relative-path with ./' => ['https://example.com:8000/path/', './other?query=value', 'https://example.com:8000/path/other?query=value'], 'relative-path with //' => ['https://example.com:8000/path/', 'other//sub?query=value', 'https://example.com:8000/path/other//sub?query=value'], 'relative-path redirect with only query' => ['https://example.com:8000/path', '?query=value', 'https://example.com:8000/path?query=value'], - ]; + ]; } /** diff --git a/tests/PluginClientTest.php b/tests/PluginClientTest.php index 6c29569..26547bd 100644 --- a/tests/PluginClientTest.php +++ b/tests/PluginClientTest.php @@ -33,14 +33,14 @@ public function testRestartChain(PluginClient $client, string $method, string $r public function clientAndMethodProvider() { - $syncClient = new class() implements ClientInterface { + $syncClient = new class implements ClientInterface { public function sendRequest(RequestInterface $request): ResponseInterface { return new Response(); } }; - $asyncClient = new class() implements HttpAsyncClient { + $asyncClient = new class implements HttpAsyncClient { public function sendAsyncRequest(RequestInterface $request) { return new HttpFulfilledPromise(new Response()); @@ -49,7 +49,7 @@ public function sendAsyncRequest(RequestInterface $request) $headerAppendPlugin = new HeaderAppendPlugin(['Content-Type' => 'text/html']); $redirectPlugin = new RedirectPlugin(); - $restartOncePlugin = new class() implements Plugin { + $restartOncePlugin = new class implements Plugin { private $firstRun = true; public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise From 83263d4e43e7f3e91d20d105efec1e5198569cd9 Mon Sep 17 00:00:00 2001 From: Eirik Stanghelle Morland Date: Mon, 23 Sep 2024 15:34:27 +0200 Subject: [PATCH 122/123] Test on PHP 8.4 * Add 8.4 to matrix --- .github/workflows/tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5c51567..145d475 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] + php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] steps: - name: Checkout code @@ -25,6 +25,10 @@ jobs: tools: composer:v2 coverage: none + - name: Emulate PHP 8.3 + run: composer config platform.php 8.3.999 + if: matrix.php == '8.4' + - name: Install PHP dependencies run: composer update --prefer-dist --no-interaction --no-progress From 0cfe9858ab9d3b213041b947c881d5b19ceeca46 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Tue, 24 Sep 2024 08:21:48 +0200 Subject: [PATCH 123/123] prepare release --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cfd5c8..31ea0ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # Change Log -## unreleased +## 2.7.2 - 2024-09-24 -- Cleaned up phpdoc. +- Updated code to not raise warnings for nullable parameters in PHP 8.4. +- Cleaned up PHPDoc comments. ## 2.7.1 - 2023-11-30