diff --git a/.gitignore b/.gitignore index 16b4a20..8ce13f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/.php_cs +/.php_cs.cache /behat.yml /build/ /composer.lock diff --git a/.php_cs b/.php_cs deleted file mode 100644 index 23ba165..0000000 --- a/.php_cs +++ /dev/null @@ -1,13 +0,0 @@ -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); 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 diff --git a/.travis.yml b/.travis.yml index 6af5f34..10d366e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,67 +1,55 @@ language: php - sudo: false -dist: trusty - cache: - directories: - - $HOME/.composer/cache/files + 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 - include: - - php: 7.1 - env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" DEPENDENCIES="doctrine/instantiator:^1.1" - - # 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" + fast_finish: true + +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.2 + env: DEPENDENCIES="dunglas/symfony-lock:^2" + - 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.3 + env: STABILITY="dev" - # 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: - # Latest dev is allowed to fail. - - 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index ff6b43c..4e5e0a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## 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`) +- 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. +- AddPathPlugin: Do not add the prefix if the URL already has the same prefix. + +### Removed +- Deprecated option `debug_plugins` has been removed from `PluginClient` + ## 1.9.0 - 2019-01-03 ### Added @@ -10,7 +25,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/composer.json b/composer.json index c502f77..821fe16 100644 --- a/composer.json +++ b/composer.json @@ -11,17 +11,22 @@ } ], "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": { + "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" @@ -31,13 +36,18 @@ "Http\\Client\\Common\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "spec\\Http\\Client\\Common\\": "spec/" + } + }, "scripts": { "test": "vendor/bin/phpspec run", "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" }, "extra": { "branch-alias": { - "dev-master": "1.9.x-dev" + "dev-master": "2.x-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/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/spec/BatchClientSpec.php b/spec/BatchClientSpec.php index 962f00a..86c6c8b 100644 --- a/spec/BatchClientSpec.php +++ b/spec/BatchClientSpec.php @@ -6,27 +6,31 @@ 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 { - function let(HttpClient $client) + public function let(HttpClient $client) { - $this->beAnInstanceOf('Http\Client\Common\BatchClient', [$client]); + $this->beAnInstanceOf(BatchClient::class, [$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); - $this->sendRequests([$request1, $request2])->shouldReturnAnInstanceOf('Http\Client\Common\BatchResult'); + $this->sendRequests([$request1, $request2])->shouldReturnAnInstanceOf(BatchResult::class); } - 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'); + $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 c4618ac..90a80a5 100644 --- a/spec/BatchResultSpec.php +++ b/spec/BatchResultSpec.php @@ -6,24 +6,25 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\BatchResult; class BatchResultSpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { - $this->beAnInstanceOf('Http\Client\Common\BatchResult'); + $this->beAnInstanceOf(BatchResult::class); } - function it_is_immutable(RequestInterface $request, ResponseInterface $response) + public function it_is_immutable(RequestInterface $request, ResponseInterface $response) { $new = $this->addResponse($request, $response); $this->getResponses()->shouldReturn([]); - $new->shouldHaveType('Http\Client\Common\BatchResult'); + $new->shouldHaveType(BatchResult::class); $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,17 +34,17 @@ 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); - $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); } - 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..226008b 100644 --- a/spec/EmulatedHttpAsyncClientSpec.php +++ b/spec/EmulatedHttpAsyncClientSpec.php @@ -6,47 +6,52 @@ 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 { - 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'); + $this->shouldHaveType(EmulatedHttpAsyncClient::class); } - function it_is_an_http_client() + public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } - function it_is_an_async_http_client() + public function it_is_an_async_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } - function it_emulates_a_successful_request( + public function it_emulates_a_successful_request( HttpClient $httpClient, RequestInterface $request, ResponseInterface $response ) { $httpClient->sendRequest($request)->willReturn($response); - $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); } - 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'); + $httpClient->sendRequest($request)->willThrow(TransferException::class); - $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); + $this->sendAsyncRequest($request)->shouldReturnAnInstanceOf(HttpRejectedPromise::class); } - 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..adf206f 100644 --- a/spec/EmulatedHttpClientSpec.php +++ b/spec/EmulatedHttpClientSpec.php @@ -3,36 +3,38 @@ namespace spec\Http\Client\Common; use Http\Client\Exception\TransferException; -use Http\Client\HttpClient; 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 { - 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'); + $this->shouldHaveType(EmulatedHttpClient::class); } - function it_is_an_http_client() + public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } - function it_is_an_async_http_client() + public function it_is_an_async_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } - function it_emulates_a_successful_request( + public function it_emulates_a_successful_request( HttpAsyncClient $httpAsyncClient, RequestInterface $request, Promise $promise, @@ -47,7 +49,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); @@ -55,10 +57,10 @@ function it_emulates_a_failed_request(HttpAsyncClient $httpAsyncClient, RequestI $httpAsyncClient->sendAsyncRequest($request)->willReturn($promise); - $this->shouldThrow('Http\Client\Exception')->duringSendRequest($request); + $this->shouldThrow(Exception::class)->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..0205843 100644 --- a/spec/Exception/BatchExceptionSpec.php +++ b/spec/Exception/BatchExceptionSpec.php @@ -5,32 +5,33 @@ use Http\Client\Common\BatchResult; use Http\Client\Exception; use PhpSpec\ObjectBehavior; +use Http\Client\Common\Exception\BatchException; 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'); + $this->shouldHaveType(BatchException::class); } - function it_is_a_runtime_exception() + public function it_is_a_runtime_exception() { - $this->shouldHaveType('RuntimeException'); + $this->shouldHaveType(\RuntimeException::class); } - function it_is_an_exception() + public function it_is_an_exception() { - $this->shouldImplement('Http\Client\Exception'); + $this->shouldImplement(Exception::class); } - function it_has_a_batch_result() + 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 70e6e4d..b63aa3d 100644 --- a/spec/FlexibleHttpClientSpec.php +++ b/spec/FlexibleHttpClientSpec.php @@ -8,37 +8,38 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\FlexibleHttpClient; 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'); + $this->shouldHaveType(FlexibleHttpClient::class); } - function it_is_an_http_client() + public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } - function it_is_an_async_http_client() + public function it_is_an_async_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } - function it_throw_exception_if_invalid_client() + public function it_throw_exception_if_invalid_client() { $this->beConstructedWith(null); - $this->shouldThrow('\LogicException')->duringInstantiation(); + $this->shouldThrow(\LogicException::class)->duringInstantiation(); } - function it_emulates_an_async_client( + public function it_emulates_an_async_client( HttpClient $httpClient, RequestInterface $syncRequest, ResponseInterface $syncResponse, @@ -53,11 +54,11 @@ 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); } - function it_emulates_a_client( + public function it_emulates_a_client( HttpAsyncClient $httpAsyncClient, RequestInterface $asyncRequest, Promise $promise, @@ -75,10 +76,10 @@ 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'); + $client->implement(HttpClient::class); + $client->implement(HttpAsyncClient::class); $client->sendRequest($syncRequest)->shouldBeCalled(); $client->sendRequest($asyncRequest)->shouldNotBeCalled(); diff --git a/spec/HttpClientPoolItemSpec.php b/spec/HttpClientPool/HttpClientPoolItemSpec.php similarity index 89% rename from spec/HttpClientPoolItemSpec.php rename to spec/HttpClientPool/HttpClientPoolItemSpec.php index 059ec8d..22c0b7d 100644 --- a/spec/HttpClientPoolItemSpec.php +++ b/spec/HttpClientPool/HttpClientPoolItemSpec.php @@ -1,6 +1,6 @@ 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) @@ -156,7 +157,7 @@ public function getState() public function wait($unwrap = true) { - if ($this->state === Promise::FULFILLED) { + if (Promise::FULFILLED === $this->state) { if (!$unwrap) { return; } @@ -164,7 +165,7 @@ public function wait($unwrap = true) return $this->response; } - if ($this->state === Promise::REJECTED) { + if (Promise::REJECTED === $this->state) { if (!$unwrap) { return; } @@ -175,7 +176,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 +184,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 +195,7 @@ public function wait($unwrap = true) } } - if ($this->response !== null) { + if (null !== $this->response) { $this->state = Promise::FULFILLED; if ($unwrap) { @@ -202,7 +203,7 @@ public function wait($unwrap = true) } } - if ($this->exception !== null) { + if (null !== $this->exception) { $this->state = Promise::REJECTED; if ($unwrap) { diff --git a/spec/HttpClientPool/LeastUsedClientPoolSpec.php b/spec/HttpClientPool/LeastUsedClientPoolSpec.php index a976c31..524af9b 100644 --- a/spec/HttpClientPool/LeastUsedClientPoolSpec.php +++ b/spec/HttpClientPool/LeastUsedClientPoolSpec.php @@ -2,37 +2,39 @@ namespace spec\Http\Client\Common\HttpClientPool; -use Http\Client\Common\HttpClientPoolItem; +use Http\Client\Common\HttpClientPool\HttpClientPoolItem; 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; 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) @@ -55,27 +57,23 @@ 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) { - 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/spec/HttpClientPool/RandomClientPoolSpec.php b/spec/HttpClientPool/RandomClientPoolSpec.php index 4054d82..317bcd8 100644 --- a/spec/HttpClientPool/RandomClientPoolSpec.php +++ b/spec/HttpClientPool/RandomClientPoolSpec.php @@ -2,7 +2,7 @@ namespace spec\Http\Client\Common\HttpClientPool; -use Http\Client\Common\HttpClientPoolItem; +use Http\Client\Common\HttpClientPool\HttpClientPoolItem; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Promise\Promise; @@ -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..1b4a243 100644 --- a/spec/HttpClientPool/RoundRobinClientPoolSpec.php +++ b/spec/HttpClientPool/RoundRobinClientPoolSpec.php @@ -2,7 +2,7 @@ namespace spec\Http\Client\Common\HttpClientPool; -use Http\Client\Common\HttpClientPoolItem; +use Http\Client\Common\HttpClientPool\HttpClientPoolItem; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Promise\Promise; @@ -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/HttpClientRouterSpec.php b/spec/HttpClientRouterSpec.php index 1119722..a409203 100644 --- a/spec/HttpClientRouterSpec.php +++ b/spec/HttpClientRouterSpec.php @@ -2,6 +2,7 @@ namespace spec\Http\Client\Common; +use Http\Client\Common\HttpClientRouter; use Http\Message\RequestMatcher; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; @@ -9,25 +10,32 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; +use Http\Client\Common\HttpClientRouterInterface; +use Http\Client\Exception\RequestException; class HttpClientRouterSpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\HttpClientRouter'); + $this->shouldHaveType(HttpClientRouter::class); } - function it_is_an_http_client() + public function it_is_an_http_client_router() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClientRouterInterface::class); } - function it_is_an_async_http_client() + public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpClient::class); } - function it_send_request(RequestMatcher $matcher, HttpClient $client, RequestInterface $request, ResponseInterface $response) + public function it_is_an_async_http_client() + { + $this->shouldImplement(HttpAsyncClient::class); + } + + public function it_send_request(RequestMatcher $matcher, HttpClient $client, RequestInterface $request, ResponseInterface $response) { $this->addClient($client, $matcher); $matcher->matches($request)->willReturn(true); @@ -36,7 +44,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,19 +53,19 @@ 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); - $this->shouldThrow('Http\Client\Exception\RequestException')->duringSendRequest($request); + $this->shouldThrow(RequestException::class)->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); - $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 07c0b47..68e124d 100644 --- a/spec/HttpMethodsClientSpec.php +++ b/spec/HttpMethodsClientSpec.php @@ -2,115 +2,88 @@ namespace spec\Http\Client\Common; -use Http\Client\BatchResult; -use Http\Client\HttpClient; use Http\Client\Common\HttpMethodsClient; -use Http\Message\MessageFactory; +use Http\Client\HttpClient; +use Http\Message\RequestFactory; +use PhpSpec\ObjectBehavior; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use PhpSpec\ObjectBehavior; class HttpMethodsClientSpec extends ObjectBehavior { - 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( - 'spec\Http\Client\Common\HttpMethodsClientStub', [ + HttpMethodsClient::class, [ $client, - $messageFactory + $requestFactory, ] ); } - 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'])->shouldReturn(true); + $this->assert($client, $requestFactory, $request, $response, 'get'); } - 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'])->shouldReturn(true); + $this->assert($client, $requestFactory, $request, $response, 'head'); } - 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'])->shouldReturn(true); + $this->assert($client, $requestFactory, $request, $response, 'trace'); } - 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'])->shouldReturn(true); + $this->assert($client, $requestFactory, $request, $response, 'post', self::$requestData['body']); } - 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'])->shouldReturn(true); + $this->assert($client, $requestFactory, $request, $response, 'put', self::$requestData['body']); } - 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'])->shouldReturn(true); + $this->assert($client, $requestFactory, $request, $response, 'patch', self::$requestData['body']); } - 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'])->shouldReturn(true); + $this->assert($client, $requestFactory, $request, $response, 'delete', self::$requestData['body']); } - 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'])->shouldReturn(true); + $this->assert($client, $requestFactory, $request, $response, 'options', self::$requestData['body']); } - 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) + private function mockFactory(RequestFactory $requestFactory, RequestInterface $request, string $method, string $body = null) { - if (in_array($method, ['GET', 'HEAD', 'TRACE'])) { - return $uri === self::$requestData['uri'] && - $headers === self::$requestData['headers'] && - is_null($body); - } - - return in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']) && - $uri === self::$requestData['uri'] && - $headers === self::$requestData['headers'] && - $body === self::$requestData['body']; + $requestFactory->createRequest($method, self::$requestData['uri'], self::$requestData['headers'], $body)->willReturn($request); } } diff --git a/spec/Plugin/AddHostPluginSpec.php b/spec/Plugin/AddHostPluginSpec.php index caf4f21..2691f10 100644 --- a/spec/Plugin/AddHostPluginSpec.php +++ b/spec/Plugin/AddHostPluginSpec.php @@ -2,34 +2,34 @@ 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; +use Http\Client\Common\Plugin\AddHostPlugin; +use Http\Client\Common\Plugin; 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'); + $this->shouldHaveType(AddHostPlugin::class); } - 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'); + $this->shouldImplement(Plugin::class); } - function it_adds_domain( + public function it_adds_domain( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -47,10 +47,10 @@ function it_adds_domain( $uri->getHost()->shouldBeCalled()->willReturn(''); $this->beConstructedWith($host); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } - function it_replaces_domain( + public function it_replaces_domain( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -67,10 +67,10 @@ 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 () {}); } - function it_does_nothing_when_domain_exists( + public function it_does_nothing_when_domain_exists( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -79,6 +79,6 @@ 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 6838ed8..1c6f09d 100644 --- a/spec/Plugin/AddPathPluginSpec.php +++ b/spec/Plugin/AddPathPluginSpec.php @@ -2,34 +2,34 @@ 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; +use Http\Client\Common\Plugin\AddPathPlugin; +use Http\Client\Common\Plugin; 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'); + $this->shouldHaveType(AddPathPlugin::class); } - 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'); + $this->shouldImplement(Plugin::class); } - function it_adds_path( + public function it_adds_path( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -37,16 +37,16 @@ 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); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } - function it_removes_ending_slashes( + public function it_removes_ending_slashes( RequestInterface $request, UriInterface $host, UriInterface $host2, @@ -63,14 +63,14 @@ function it_removes_ending_slashes( $uri->getPath()->shouldBeCalled()->willReturn('/users'); $this->beConstructedWith($host); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } - function it_throws_exception_on_empty_path(UriInterface $host) + 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 02d1187..c86cb3a 100644 --- a/spec/Plugin/AuthenticationPluginSpec.php +++ b/spec/Plugin/AuthenticationPluginSpec.php @@ -7,29 +7,31 @@ 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 { - 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'); + $this->shouldHaveType(AuthenticationPlugin::class); } - function it_is_a_plugin() + public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } - 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..92f5074 100644 --- a/spec/Plugin/BaseUriPluginSpec.php +++ b/spec/Plugin/BaseUriPluginSpec.php @@ -2,36 +2,36 @@ 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; +use Http\Client\Common\Plugin\BaseUriPlugin; +use Http\Client\Common\Plugin; 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'); - $this->shouldHaveType('Http\Client\Common\Plugin\BaseUriPlugin'); + $this->shouldHaveType(BaseUriPlugin::class); } - 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'); - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } - function it_adds_domain_and_path( + public function it_adds_domain_and_path( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -52,10 +52,10 @@ 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 () {}); } - function it_adds_domain( + public function it_adds_domain( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -74,10 +74,10 @@ function it_adds_domain( $uri->getHost()->shouldBeCalled()->willReturn(''); $this->beConstructedWith($host); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), function () {}); } - function it_replaces_domain_and_adds_path( + public function it_replaces_domain_and_adds_path( RequestInterface $request, UriInterface $host, UriInterface $uri @@ -97,6 +97,6 @@ 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 4ec2ba7..a945924 100644 --- a/spec/Plugin/ContentLengthPluginSpec.php +++ b/spec/Plugin/ContentLengthPluginSpec.php @@ -7,32 +7,34 @@ 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 { - function it_is_initializable() + public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\ContentLengthPlugin'); + $this->shouldHaveType(ContentLengthPlugin::class); } - function it_is_a_plugin() + public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } - 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); $stream->getSize()->shouldBeCalled()->willReturn(100); $request->withHeader('Content-Length', '100')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), 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'); } @@ -43,6 +45,6 @@ function it_streams_chunked_if_no_size(RequestInterface $request, StreamInterfac $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 3df7d87..a27d32a 100644 --- a/spec/Plugin/ContentTypePluginSpec.php +++ b/spec/Plugin/ContentTypePluginSpec.php @@ -2,108 +2,106 @@ namespace spec\Http\Client\Common\Plugin; -use PhpSpec\Exception\Example\SkippingException; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\ContentTypePlugin; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; +use Psr\Http\Message\RequestInterface; class ContentTypePluginSpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\ContentTypePlugin'); + $this->shouldHaveType(ContentTypePlugin::class); } - function it_is_a_plugin() + public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } - 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']))); $request->withHeader('Content-Type', 'application/json')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), 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')); $request->withHeader('Content-Type', 'application/xml')->shouldBeCalled()->willReturn($request); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), 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')); $request->withHeader('Content-Type', null)->shouldNotBeCalled(); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), 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')); $request->withHeader('Content-Type', null)->shouldNotBeCalled(); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), 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()); $request->withHeader('Content-Type', null)->shouldNotBeCalled(); - $this->handleRequest($request, function () {}, function () {}); + $this->handleRequest($request, PluginStub::next(), 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); $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 () {}); } - 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); $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 () {}); } - 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); $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 2bb47e7..7bb188c 100644 --- a/spec/Plugin/CookiePluginSpec.php +++ b/spec/Plugin/CookiePluginSpec.php @@ -10,30 +10,33 @@ 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 { 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'); + $this->shouldHaveType(CookiePlugin::class); } - function it_is_a_plugin() + public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } - 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); @@ -44,14 +47,10 @@ function it_loads_cookie(RequestInterface $request, UriInterface $uri, Promise $ $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 () {}); } - 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'); @@ -65,28 +64,20 @@ function it_combines_multiple_cookies_into_one_header(RequestInterface $request, $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 () {}); } - 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); $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 () {}); } - 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); @@ -96,14 +87,10 @@ function it_does_not_load_cookie_if_domain_does_not_match(RequestInterface $requ $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 () {}); } - 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', @@ -118,15 +105,11 @@ function it_does_not_load_cookie_on_hackish_domains(RequestInterface $request, U $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 () {}); } } - 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); @@ -137,14 +120,10 @@ function it_loads_cookie_on_subdomains(RequestInterface $request, UriInterface $ $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 () {}); } - 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); @@ -155,14 +134,10 @@ function it_does_not_load_cookie_if_path_does_not_match(RequestInterface $reques $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 () {}); } - 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); @@ -174,14 +149,10 @@ function it_does_not_load_cookie_when_cookie_is_secure(RequestInterface $request $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 () {}); } - 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); @@ -193,14 +164,10 @@ function it_loads_cookie_when_cookie_is_secure(RequestInterface $request, UriInt $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 () {}); } - 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 +175,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); @@ -216,11 +183,11 @@ function it_saves_cookie(RequestInterface $request, ResponseInterface $response, $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); } - 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 +198,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); @@ -239,7 +206,7 @@ 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 7543027..1316a90 100644 --- a/spec/Plugin/DecoderPluginSpec.php +++ b/spec/Plugin/DecoderPluginSpec.php @@ -9,28 +9,32 @@ 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 { - function it_is_initializable() + public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\DecoderPlugin'); + $this->shouldHaveType(DecoderPlugin::class); } - function it_is_a_plugin() + public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } - 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 +52,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()); }; @@ -60,7 +64,7 @@ function it_decodes_gzip(RequestInterface $request, ResponseInterface $response, $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); @@ -70,11 +74,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()); }; @@ -82,7 +86,7 @@ function it_decodes_deflate(RequestInterface $request, ResponseInterface $respon $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); @@ -92,13 +96,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..67e5c7e 100644 --- a/spec/Plugin/ErrorPluginSpec.php +++ b/spec/Plugin/ErrorPluginSpec.php @@ -7,77 +7,82 @@ 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 { - function it_is_initializable() + public function it_is_initializable() { - $this->beAnInstanceOf('Http\Client\Common\Plugin\ErrorPlugin'); + $this->beAnInstanceOf(ErrorPlugin::class); } - function it_is_a_plugin() + public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } - 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()); } }; $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(); } - 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()); } }; - $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); } - 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()); } }; $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(); } - 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()); } }; - $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 24b8565..9325069 100644 --- a/spec/Plugin/HeaderAppendPluginSpec.php +++ b/spec/Plugin/HeaderAppendPluginSpec.php @@ -2,36 +2,35 @@ namespace spec\Http\Client\Common\Plugin; -use PhpSpec\Exception\Example\SkippingException; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\HeaderAppendPlugin; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; +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) { $this->beConstructedWith([ - 'foo'=>'bar', - 'baz'=>'qux' + 'foo' => 'bar', + 'baz' => 'qux', ]); $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 341f1a5..5a50a9c 100644 --- a/spec/Plugin/HeaderDefaultsPluginSpec.php +++ b/spec/Plugin/HeaderDefaultsPluginSpec.php @@ -2,37 +2,36 @@ namespace spec\Http\Client\Common\Plugin; -use PhpSpec\Exception\Example\SkippingException; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\HeaderDefaultsPlugin; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; +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) { $this->beConstructedWith([ 'foo' => 'bar', - 'baz' => 'qux' + 'baz' => 'qux', ]); $request->hasHeader('foo')->shouldBeCalled()->willReturn(false); $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 9ea2752..3f60359 100644 --- a/spec/Plugin/HeaderRemovePluginSpec.php +++ b/spec/Plugin/HeaderRemovePluginSpec.php @@ -2,31 +2,30 @@ namespace spec\Http\Client\Common\Plugin; -use PhpSpec\Exception\Example\SkippingException; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\HeaderRemovePlugin; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; +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) { $this->beConstructedWith([ 'foo', - 'baz' + 'baz', ]); $request->hasHeader('foo')->shouldBeCalled()->willReturn(false); @@ -34,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 f4a340c..b152567 100644 --- a/spec/Plugin/HeaderSetPluginSpec.php +++ b/spec/Plugin/HeaderSetPluginSpec.php @@ -2,36 +2,35 @@ namespace spec\Http\Client\Common\Plugin; -use PhpSpec\Exception\Example\SkippingException; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\HeaderSetPlugin; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; +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) { $this->beConstructedWith([ - 'foo'=>'bar', - 'baz'=>'qux' + 'foo' => 'bar', + 'baz' => 'qux', ]); $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 24e7f51..495e5d5 100644 --- a/spec/Plugin/HistoryPluginSpec.php +++ b/spec/Plugin/HistoryPluginSpec.php @@ -10,27 +10,28 @@ use Psr\Http\Message\ResponseInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; +use Http\Client\Common\Plugin; 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'); + $this->shouldImplement(Plugin::class); } - 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 +42,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/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) @@ -35,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) @@ -52,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 97197e1..de9b30a 100644 --- a/spec/Plugin/RedirectPluginSpec.php +++ b/spec/Plugin/RedirectPluginSpec.php @@ -2,28 +2,33 @@ 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 { - function it_is_initializable() + public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\RedirectPlugin'); + $this->shouldHaveType(RedirectPlugin::class); } - function it_is_a_plugin() + public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } - function it_redirects_on_302( + public function it_redirects_on_302( UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, @@ -48,14 +53,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(); } @@ -65,55 +69,23 @@ 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); } - 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'); - - $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, function () {}); - } - - 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('spec\Http\Client\Common\Plugin\RedirectPluginStub'); - $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); @@ -121,26 +93,58 @@ 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); } - function it_replace_full_url( + 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( UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, @@ -171,13 +175,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 +193,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()); } @@ -203,13 +207,13 @@ function it_throws_http_exception_on_no_location(RequestInterface $request, UriI $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(); } - 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()); } @@ -223,13 +227,13 @@ function it_throws_http_exception_on_invalid_location(RequestInterface $request, $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(); } - 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()); } @@ -239,13 +243,13 @@ function it_throw_multi_redirect_exception_on_300(RequestInterface $request, Res $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(); } - 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()); } @@ -255,11 +259,11 @@ function it_throw_multi_redirect_exception_on_300_if_no_location(RequestInterfac $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(); } - function it_switch_method_for_302( + public function it_switch_method_for_302( UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, @@ -288,13 +292,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 +310,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 +342,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,41 +360,106 @@ 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) - { - $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('spec\Http\Client\Common\Plugin\RedirectPluginStubCircular'); - $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('Http\Client\Promise\HttpRejectedPromise'); - $promise->shouldThrow('Http\Client\Common\Exception\CircularRedirectionException')->duringWait(); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); + $promise->shouldThrow(CircularRedirectionException::class)->duringWait(); } - function it_redirects_http_to_https( + public function it_redirects_http_to_https( UriInterface $uri, UriInterface $uriRedirect, RequestInterface $request, @@ -417,13 +486,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(); } @@ -433,37 +502,7 @@ 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); } } - -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/spec/Plugin/RequestMatcherPluginSpec.php b/spec/Plugin/RequestMatcherPluginSpec.php index 4fe9aea..de7cae6 100644 --- a/spec/Plugin/RequestMatcherPluginSpec.php +++ b/spec/Plugin/RequestMatcherPluginSpec.php @@ -8,25 +8,26 @@ use Psr\Http\Message\RequestInterface; use PhpSpec\ObjectBehavior; use Prophecy\Argument; +use Http\Client\Common\Plugin\RequestMatcherPlugin; 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'); + $this->shouldHaveType(RequestMatcherPlugin::class); } - function it_is_a_plugin() + public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } - 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 @@ -34,10 +35,10 @@ 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 () {}); } - function it_does_not_match_a_request( + public function it_does_not_match_a_request( RequestInterface $request, RequestMatcher $requestMatcher, Plugin $plugin, @@ -46,7 +47,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 4a985f3..e01ba54 100644 --- a/spec/Plugin/RetryPluginSpec.php +++ b/spec/Plugin/RetryPluginSpec.php @@ -9,88 +9,112 @@ 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 { - function it_is_initializable() + public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\Plugin\RetryPlugin'); + $this->shouldHaveType(RetryPlugin::class); } - function it_is_a_plugin() + public function it_is_a_plugin() { - $this->shouldImplement('Http\Client\Common\Plugin'); + $this->shouldImplement(Plugin::class); } - 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()); } }; - $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); } - 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); } } }; $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise'); + $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class); $promise->shouldThrow($exception2)->duringWait(); } - function it_returns_response_on_second_try(RequestInterface $request, ResponseInterface $response) + 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()); $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()); } } }; $promise = $this->handleRequest($request, $next, function () {}); - $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); + $promise->shouldReturnAnInstanceOf(HttpFulfilledPromise::class); $promise->wait()->shouldReturn($response); } - function it_respects_custom_exception_decider(RequestInterface $request, ResponseInterface $response) + public function it_respects_custom_exception_decider(RequestInterface $request, ResponseInterface $response) { $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'); } @@ -104,29 +128,29 @@ function it_respects_custom_exception_decider(RequestInterface $request, Respons $promise->shouldThrow($exception)->duringWait(); } - 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()); } } }; - $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); } - 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..d7a3acb 100644 --- a/spec/PluginClientFactorySpec.php +++ b/spec/PluginClientFactorySpec.php @@ -4,22 +4,24 @@ use Http\Client\HttpClient; use PhpSpec\ObjectBehavior; +use Http\Client\Common\PluginClientFactory; +use Http\Client\Common\PluginClient; class PluginClientFactorySpec extends ObjectBehavior { - function it_is_initializable() + public function it_is_initializable() { - $this->shouldHaveType('Http\Client\Common\PluginClientFactory'); + $this->shouldHaveType(PluginClientFactory::class); } - 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'); + $client->shouldHaveType(PluginClient::class); } - 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 addb2e8..c5c2932 100644 --- a/spec/PluginClientSpec.php +++ b/spec/PluginClientSpec.php @@ -4,44 +4,45 @@ 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; 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 { - 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'); + $this->shouldHaveType(PluginClient::class); } - function it_is_an_http_client() + public function it_is_an_http_client() { - $this->shouldImplement('Http\Client\HttpClient'); + $this->shouldImplement(HttpClient::class); } - function it_is_an_http_async_client() + public function it_is_an_http_async_client() { - $this->shouldImplement('Http\Client\HttpAsyncClient'); + $this->shouldImplement(HttpAsyncClient::class); } - 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 +50,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,10 +59,10 @@ 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'); + $client->implement(HttpClient::class); + $client->implement(HttpAsyncClient::class); $client->sendRequest($request)->willReturn($response); @@ -70,7 +71,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( @@ -85,49 +86,6 @@ function it_throws_loop_exception(HttpClient $httpClient, RequestInterface $requ $this->beConstructedWith($httpClient, [$plugin]); - $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); + $this->shouldThrow(LoopException::class)->duringSendRequest($request); } } diff --git a/src/BatchClient.php b/src/BatchClient.php index 1aa9950..bd340af 100644 --- a/src/BatchClient.php +++ b/src/BatchClient.php @@ -1,62 +1,33 @@ - */ -class BatchClient implements HttpClient +final class BatchClient implements BatchClientInterface { /** - * @var HttpClient|ClientInterface + * @var ClientInterface */ private $client; - /** - * @param HttpClient|ClientInterface $client - */ - public function __construct($client) + public function __construct(ClientInterface $client) { - if (!($client instanceof HttpClient) && !($client instanceof ClientInterface)) { - throw new \LogicException('Client must be an instance of Http\\Client\\HttpClient or Psr\\Http\\Client\\ClientInterface'); - } - $this->client = $client; } - /** - * {@inheritdoc} - */ - public function sendRequest(RequestInterface $request) + 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) + 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..56a34ae --- /dev/null +++ b/src/BatchClientInterface.php @@ -0,0 +1,35 @@ + + */ +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/BatchResult.php b/src/BatchResult.php index 710611d..4202c70 100644 --- a/src/BatchResult.php +++ b/src/BatchResult.php @@ -1,5 +1,7 @@ responses->count() > 0; } @@ -44,7 +44,7 @@ public function hasResponses() * * @return ResponseInterface[] */ - public function getResponses() + public function getResponses(): array { $responses = []; @@ -57,12 +57,8 @@ public function getResponses() /** * Checks if there is a successful response for a request. - * - * @param RequestInterface $request - * - * @return bool */ - public function isSuccessful(RequestInterface $request) + public function isSuccessful(RequestInterface $request): bool { return $this->responses->contains($request); } @@ -70,13 +66,10 @@ 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 */ - public function getResponseFor(RequestInterface $request) + public function getResponseFor(RequestInterface $request): ResponseInterface { try { return $this->responses[$request]; @@ -88,12 +81,9 @@ 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) + public function addResponse(RequestInterface $request, ResponseInterface $response): self { $new = clone $this; $new->responses->attach($request, $response); @@ -103,10 +93,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; } @@ -116,7 +104,7 @@ public function hasExceptions() * * @return Exception[] */ - public function getExceptions() + public function getExceptions(): array { $exceptions = []; @@ -129,12 +117,8 @@ 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) + public function isFailed(RequestInterface $request): bool { return $this->exceptions->contains($request); } @@ -142,13 +126,10 @@ 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 */ - public function getExceptionFor(RequestInterface $request) + public function getExceptionFor(RequestInterface $request): Exception { try { return $this->exceptions[$request]; @@ -160,12 +141,9 @@ 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) + 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..7c3f9f5 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -1,5 +1,7 @@ waitCallback); @@ -69,7 +71,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/EmulatedHttpAsyncClient.php b/src/EmulatedHttpAsyncClient.php index 39f89cc..008f888 100644 --- a/src/EmulatedHttpAsyncClient.php +++ b/src/EmulatedHttpAsyncClient.php @@ -1,5 +1,7 @@ */ -class EmulatedHttpAsyncClient implements HttpClient, HttpAsyncClient +final class EmulatedHttpAsyncClient implements HttpClient, HttpAsyncClient { use HttpAsyncClientEmulator; use HttpClientDecorator; - /** - * @param HttpClient|ClientInterface $httpClient - */ - public function __construct($httpClient) + public function __construct(ClientInterface $httpClient) { - if (!($httpClient instanceof HttpClient) && !($httpClient instanceof ClientInterface)) { - throw new \LogicException('Client must be an instance of Http\\Client\\HttpClient or Psr\\Http\\Client\\ClientInterface'); - } - $this->httpClient = $httpClient; } } diff --git a/src/EmulatedHttpClient.php b/src/EmulatedHttpClient.php index 01046c8..5c2d8c4 100644 --- a/src/EmulatedHttpClient.php +++ b/src/EmulatedHttpClient.php @@ -1,25 +1,22 @@ */ -class EmulatedHttpClient implements HttpClient, HttpAsyncClient +final 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..5167842 100644 --- a/src/Exception/BatchException.php +++ b/src/Exception/BatchException.php @@ -1,5 +1,7 @@ result = $result; diff --git a/src/Exception/CircularRedirectionException.php b/src/Exception/CircularRedirectionException.php index 73ec521..de2063e 100644 --- a/src/Exception/CircularRedirectionException.php +++ b/src/Exception/CircularRedirectionException.php @@ -1,5 +1,7 @@ httpClient = $client; $this->httpAsyncClient = $client; - if (!($this->httpClient instanceof HttpClient) && !($client instanceof ClientInterface)) { + if (!($this->httpClient instanceof ClientInterface)) { $this->httpClient = new EmulatedHttpClient($this->httpClient); } diff --git a/src/HttpAsyncClientDecorator.php b/src/HttpAsyncClientDecorator.php index 6eb576c..2714b4a 100644 --- a/src/HttpAsyncClientDecorator.php +++ b/src/HttpAsyncClientDecorator.php @@ -1,5 +1,7 @@ httpClient->sendRequest($request); } diff --git a/src/HttpClientEmulator.php b/src/HttpClientEmulator.php index dbec1ab..51e2c05 100644 --- a/src/HttpClientEmulator.php +++ b/src/HttpClientEmulator.php @@ -1,8 +1,11 @@ sendAsyncRequest($request); diff --git a/src/HttpClientPool.php b/src/HttpClientPool.php index 7ac292c..9e979eb 100644 --- a/src/HttpClientPool.php +++ b/src/HttpClientPool.php @@ -1,59 +1,24 @@ 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(); - - /** - * {@inheritdoc} - */ - public function sendAsyncRequest(RequestInterface $request) - { - return $this->chooseHttpClient()->sendAsyncRequest($request); - } - - /** - * {@inheritdoc} + * @param ClientInterface|HttpAsyncClient|HttpClientPoolItem $client */ - public function sendRequest(RequestInterface $request) - { - 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..0c417c0 --- /dev/null +++ b/src/HttpClientPool/HttpClientPool.php @@ -0,0 +1,63 @@ +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 67% rename from src/HttpClientPoolItem.php rename to src/HttpClientPool/HttpClientPoolItem.php index 09cd6dd..47c9eb2 100644 --- a/src/HttpClientPoolItem.php +++ b/src/HttpClientPool/HttpClientPoolItem.php @@ -1,17 +1,29 @@ */ @@ -28,7 +40,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; @@ -38,8 +54,8 @@ class HttpClientPoolItem implements HttpClient, HttpAsyncClient private $client; /** - * @param HttpClient|HttpAsyncClient $client - * @param null|int $reenableAfter Number of seconds after this client is reenable + * @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) { @@ -50,7 +66,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); @@ -96,21 +112,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; @@ -119,31 +130,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 6299cce..789c357 100644 --- a/src/HttpClientPool/LeastUsedClientPool.php +++ b/src/HttpClientPool/LeastUsedClientPool.php @@ -1,10 +1,10 @@ clientPool, function (HttpClientPoolItem $clientPoolItem) { return !$clientPoolItem->isDisabled(); diff --git a/src/HttpClientPool/RandomClientPool.php b/src/HttpClientPool/RandomClientPool.php index 3255f86..789ba42 100644 --- a/src/HttpClientPool/RandomClientPool.php +++ b/src/HttpClientPool/RandomClientPool.php @@ -1,10 +1,10 @@ clientPool, function (HttpClientPoolItem $clientPoolItem) { return !$clientPoolItem->isDisabled(); diff --git a/src/HttpClientPool/RoundRobinClientPool.php b/src/HttpClientPool/RoundRobinClientPool.php index 8d8e40a..7c7b191 100644 --- a/src/HttpClientPool/RoundRobinClientPool.php +++ b/src/HttpClientPool/RoundRobinClientPool.php @@ -1,9 +1,10 @@ clientPool); diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index 9f72133..37f8c3c 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -1,19 +1,23 @@ */ -final class HttpClientRouter implements HttpClient, HttpAsyncClient +final class HttpClientRouter implements HttpClientRouterInterface { /** * @var array @@ -23,11 +27,9 @@ final class HttpClientRouter implements HttpClient, HttpAsyncClient /** * {@inheritdoc} */ - public function sendRequest(RequestInterface $request) + public function sendRequest(RequestInterface $request): ResponseInterface { - $client = $this->chooseHttpClient($request); - - return $client->sendRequest($request); + return $this->chooseHttpClient($request)->sendRequest($request); } /** @@ -35,16 +37,13 @@ public function sendRequest(RequestInterface $request) */ public function sendAsyncRequest(RequestInterface $request) { - $client = $this->chooseHttpClient($request); - - return $client->sendAsyncRequest($request); + return $this->chooseHttpClient($request)->sendAsyncRequest($request); } /** * Add a client to the router. * * @param HttpClient|HttpAsyncClient $client - * @param RequestMatcher $requestMatcher */ public function addClient($client, RequestMatcher $requestMatcher) { @@ -57,11 +56,9 @@ public function addClient($client, RequestMatcher $requestMatcher) /** * Choose an HTTP client given a specific request. * - * @param RequestInterface $request - * - * @return HttpClient|HttpAsyncClient + * @return ClientInterface|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/HttpClientRouterInterface.php b/src/HttpClientRouterInterface.php new file mode 100644 index 0000000..4526e7f --- /dev/null +++ b/src/HttpClientRouterInterface.php @@ -0,0 +1,27 @@ + + */ +interface HttpClientRouterInterface extends HttpClient, HttpAsyncClient +{ + /** + * Add a client to the router. + * + * @param ClientInterface|HttpAsyncClient $client + */ + public function addClient($client, RequestMatcher $requestMatcher); +} diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index c462c10..9c53c71 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -1,34 +1,18 @@ 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|ClientInterface + * @var ClientInterface */ private $httpClient; @@ -38,157 +22,56 @@ class HttpMethodsClient implements HttpClient private $requestFactory; /** - * @param HttpClient|ClientInterface $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, RequestFactory $requestFactory) + public function __construct(ClientInterface $httpClient, RequestFactory $requestFactory) { - if (!($httpClient instanceof HttpClient) && !($httpClient instanceof ClientInterface)) { - throw new \LogicException('Client must be an instance of Http\\Client\\HttpClient or Psr\\Http\\Client\\ClientInterface'); - } - $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; } - /** - * Sends a GET request. - * - * @param string|UriInterface $uri - * @param array $headers - * - * @throws Exception - * - * @return ResponseInterface - */ - public function get($uri, array $headers = []) + public function get($uri, array $headers = []): ResponseInterface { return $this->send('GET', $uri, $headers, null); } - /** - * Sends an HEAD request. - * - * @param string|UriInterface $uri - * @param array $headers - * - * @throws Exception - * - * @return ResponseInterface - */ - public function head($uri, array $headers = []) + public function head($uri, array $headers = []): ResponseInterface { return $this->send('HEAD', $uri, $headers, null); } - /** - * Sends a TRACE request. - * - * @param string|UriInterface $uri - * @param array $headers - * - * @throws Exception - * - * @return ResponseInterface - */ - public function trace($uri, array $headers = []) + public function trace($uri, array $headers = []): ResponseInterface { return $this->send('TRACE', $uri, $headers, null); } - /** - * Sends a POST request. - * - * @param string|UriInterface $uri - * @param 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); } - /** - * Sends a PUT request. - * - * @param string|UriInterface $uri - * @param array $headers - * @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); } - /** - * Sends a PATCH request. - * - * @param string|UriInterface $uri - * @param array $headers - * @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); } - /** - * Sends a DELETE request. - * - * @param string|UriInterface $uri - * @param array $headers - * @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); } - /** - * Sends an OPTIONS request. - * - * @param string|UriInterface $uri - * @param array $headers - * @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); } - /** - * Sends a request with any HTTP method. - * - * @param string $method HTTP method to use - * @param string|UriInterface $uri - * @param array $headers - * @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, @@ -198,12 +81,7 @@ public function send($method, $uri, array $headers = [], $body = null) )); } - /** - * Forward to the underlying HttpClient. - * - * {@inheritdoc} - */ - public function sendRequest(RequestInterface $request) + 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..20fbf03 --- /dev/null +++ b/src/HttpMethodsClientInterface.php @@ -0,0 +1,116 @@ +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; +} diff --git a/src/Plugin.php b/src/Plugin.php index 89a2a62..cbc9592 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -1,5 +1,7 @@ replace || '' === $request->getUri()->getHost()) { $uri = $request->getUri() @@ -64,9 +66,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 0a1bca2..9d43104 100644 --- a/src/Plugin/AddPathPlugin.php +++ b/src/Plugin/AddPathPlugin.php @@ -1,8 +1,11 @@ getPath()) { @@ -42,17 +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) + 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)) { + if (substr($path, 0, strlen($prepend)) !== $prepend) { $request = $request->withUri($request->getUri() - ->withPath($this->uri->getPath().$request->getUri()->getPath()) - ); - $this->alteredRequests[$identifier] = $identifier; + ->withPath($prepend.$path) + ); } return $next($request); diff --git a/src/Plugin/AuthenticationPlugin.php b/src/Plugin/AuthenticationPlugin.php index 194712f..ce9d4bd 100644 --- a/src/Plugin/AuthenticationPlugin.php +++ b/src/Plugin/AuthenticationPlugin.php @@ -1,9 +1,12 @@ authentication = $authentication; @@ -29,7 +29,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 2c2a775..825709b 100644 --- a/src/Plugin/BaseUriPlugin.php +++ b/src/Plugin/BaseUriPlugin.php @@ -1,8 +1,11 @@ addHostPlugin->handleRequest($request, $next, $first); diff --git a/src/Plugin/ContentLengthPlugin.php b/src/Plugin/ContentLengthPlugin.php index 0f7aafa..f313c33 100644 --- a/src/Plugin/ContentLengthPlugin.php +++ b/src/Plugin/ContentLengthPlugin.php @@ -1,9 +1,12 @@ hasHeader('Content-Length')) { $stream = $request->getBody(); diff --git a/src/Plugin/ContentTypePlugin.php b/src/Plugin/ContentTypePlugin.php index 8ef1d62..f3944dd 100644 --- a/src/Plugin/ContentTypePlugin.php +++ b/src/Plugin/ContentTypePlugin.php @@ -1,8 +1,11 @@ hasHeader('Content-Type')) { $stream = $request->getBody(); @@ -91,13 +94,11 @@ public function handleRequest(RequestInterface $request, callable $next, callabl return $next($request); } - /** - * @param $stream StreamInterface - * - * @return bool - */ - private function isJson($stream) + private function isJson(StreamInterface $stream): bool { + if (!function_exists('json_decode')) { + return false; + } $stream->rewind(); json_decode($stream->getContents()); @@ -107,17 +108,18 @@ private function isJson($stream) /** * @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 156532a..8399210 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -1,5 +1,7 @@ cookieJar = $cookieJar; @@ -36,7 +36,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) { @@ -91,19 +91,16 @@ public function handleRequest(RequestInterface $request, callable $next, callabl /** * Creates a cookie from a string. * - * @param RequestInterface $request - * @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; + return null; } list($name, $cookieValue) = $this->createValueKey(array_shift($parts)); @@ -170,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 0239d40..271ad3c 100644 --- a/src/Plugin/DecoderPlugin.php +++ b/src/Plugin/DecoderPlugin.php @@ -1,9 +1,12 @@ decodeOnEncodingHeader('Transfer-Encoding', $response); @@ -83,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); @@ -120,12 +114,9 @@ private function decodeOnEncodingHeader($headerName, ResponseInterface $response /** * Decorate a stream given an encoding. * - * @param string $encoding - * @param StreamInterface $stream - * * @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); diff --git a/src/Plugin/ErrorPlugin.php b/src/Plugin/ErrorPlugin.php index bcc1c67..2d1d2f1 100644 --- a/src/Plugin/ErrorPlugin.php +++ b/src/Plugin/ErrorPlugin.php @@ -1,10 +1,13 @@ 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..95ea673 100644 --- a/src/Plugin/HeaderAppendPlugin.php +++ b/src/Plugin/HeaderAppendPlugin.php @@ -1,8 +1,11 @@ headers as $header => $headerValue) { $request = $request->withAddedHeader($header, $headerValue); diff --git a/src/Plugin/HeaderDefaultsPlugin.php b/src/Plugin/HeaderDefaultsPlugin.php index 6dfc111..bf58070 100644 --- a/src/Plugin/HeaderDefaultsPlugin.php +++ b/src/Plugin/HeaderDefaultsPlugin.php @@ -1,8 +1,11 @@ headers as $header => $headerValue) { if (!$request->hasHeader($header)) { diff --git a/src/Plugin/HeaderRemovePlugin.php b/src/Plugin/HeaderRemovePlugin.php index fc9c19d..9f4ca44 100644 --- a/src/Plugin/HeaderRemovePlugin.php +++ b/src/Plugin/HeaderRemovePlugin.php @@ -1,8 +1,11 @@ headers as $header) { if ($request->hasHeader($header)) { diff --git a/src/Plugin/HeaderSetPlugin.php b/src/Plugin/HeaderSetPlugin.php index 75f11d4..06f00eb 100644 --- a/src/Plugin/HeaderSetPlugin.php +++ b/src/Plugin/HeaderSetPlugin.php @@ -1,8 +1,11 @@ headers as $header => $headerValue) { $request = $request->withHeader($header, $headerValue); diff --git a/src/Plugin/HistoryPlugin.php b/src/Plugin/HistoryPlugin.php index 5abddbd..a147d2e 100644 --- a/src/Plugin/HistoryPlugin.php +++ b/src/Plugin/HistoryPlugin.php @@ -1,9 +1,12 @@ journal = $journal; @@ -32,7 +32,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/Journal.php b/src/Plugin/Journal.php index 15f3095..46f0a7b 100644 --- a/src/Plugin/Journal.php +++ b/src/Plugin/Journal.php @@ -1,5 +1,7 @@ getUri(); diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php index d2f442e..e562553 100644 --- a/src/Plugin/RedirectPlugin.php +++ b/src/Plugin/RedirectPlugin.php @@ -1,11 +1,14 @@ */ -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'], @@ -78,26 +81,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 { @@ -131,7 +134,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)) { @@ -142,7 +145,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)) { @@ -170,7 +173,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl ]; } - // Call redirect request in synchrone + // Call redirect request synchronously $redirectPromise = $first($redirectRequest); return $redirectPromise->wait(); @@ -186,7 +189,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); @@ -215,10 +218,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 5f72b02..fdf9377 100644 --- a/src/Plugin/RequestMatcherPlugin.php +++ b/src/Plugin/RequestMatcherPlugin.php @@ -1,9 +1,12 @@ requestMatcher = $requestMatcher; @@ -36,7 +35,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 3d09265..5292ddf 100644 --- a/src/Plugin/RetryPlugin.php +++ b/src/Plugin/RetryPlugin.php @@ -1,9 +1,13 @@ setDefaults([ 'retries' => 1, 'exception_decider' => function (RequestInterface $request, Exception $e) { - return true; + // do not retry client errors + return !$e instanceof HttpException || $e->getCode() >= 500; }, 'exception_delay' => __CLASS__.'::defaultDelay', ]); @@ -89,7 +94,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); @@ -117,7 +122,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl $time = call_user_func($this->exceptionDelay, $request, $exception, $this->retryStorage[$chainIdentifier]); usleep($time); - // Retry in synchrone + // Retry synchronously ++$this->retryStorage[$chainIdentifier]; $promise = $this->handleRequest($request, $next, $first); @@ -126,9 +131,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/Plugin/VersionBridgePlugin.php b/src/Plugin/VersionBridgePlugin.php index f3891e5..0a2c714 100644 --- a/src/Plugin/VersionBridgePlugin.php +++ b/src/Plugin/VersionBridgePlugin.php @@ -1,7 +1,10 @@ doHandleRequest($request, $next, $first); } diff --git a/src/PluginClient.php b/src/PluginClient.php index 8cedcf6..696d3ef 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -1,5 +1,7 @@ client = $client; - } elseif ($client instanceof HttpClient || $client instanceof ClientInterface) { + } 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; @@ -68,7 +70,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)) { @@ -102,35 +104,15 @@ public function sendAsyncRequest(RequestInterface $request) /** * Configure the plugin client. - * - * @param array $options - * - * @return array */ - private function configure(array $options = []) + private function configure(array $options = []): array { - 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); } @@ -140,23 +122,12 @@ 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; - /* - * 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); }; diff --git a/src/PluginClientFactory.php b/src/PluginClientFactory.php index cbea3e1..6ae5091 100644 --- a/src/PluginClientFactory.php +++ b/src/PluginClientFactory.php @@ -1,9 +1,10 @@ doSendRequest($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); + } +} diff --git a/tests/PluginClientTest.php b/tests/PluginClientTest.php new file mode 100644 index 0000000..eb55685 --- /dev/null +++ b/tests/PluginClientTest.php @@ -0,0 +1,79 @@ +assertInstanceOf($returnType, $result); + } + + 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); + } + $this->firstRun = true; + + 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]; + } +}