diff --git a/.gitattributes b/.gitattributes
index c47225a..9e28cc7 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,15 +1,15 @@
-.editorconfig export-ignore
-.gitattributes export-ignore
-/.github/ export-ignore
-.gitignore export-ignore
-/.php_cs export-ignore
-/.scrutinizer.yml export-ignore
-/.styleci.yml export-ignore
-/.travis.yml export-ignore
-/behat.yml.dist export-ignore
-/features/ export-ignore
-/phpspec.ci.yml export-ignore
-/phpspec.yml.dist export-ignore
-/phpunit.xml.dist export-ignore
-/spec/ export-ignore
-/tests/ export-ignore
+.editorconfig export-ignore
+.gitattributes export-ignore
+/.github/ export-ignore
+.gitignore export-ignore
+/.php_cs export-ignore
+/.scrutinizer.yml export-ignore
+/.styleci.yml export-ignore
+/behat.yml.dist export-ignore
+/features/ export-ignore
+/phpspec.ci.yml export-ignore
+/phpspec.yml.dist export-ignore
+/phpunit.xml.dist export-ignore
+/phpstan.neon.dist export-ignore
+/spec/ export-ignore
+/tests/ export-ignore
diff --git a/.github/workflows/.editorconfig b/.github/workflows/.editorconfig
new file mode 100644
index 0000000..7bd3346
--- /dev/null
+++ b/.github/workflows/.editorconfig
@@ -0,0 +1,2 @@
+[*.yml]
+indent_size = 2
diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml
new file mode 100644
index 0000000..7585030
--- /dev/null
+++ b/.github/workflows/static.yml
@@ -0,0 +1,39 @@
+name: static
+
+on:
+ push:
+ branches:
+ - '*.x'
+ pull_request:
+
+jobs:
+ phpstan:
+ name: PHPStan
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Remove phpspec
+ run: composer remove --dev friends-of-phpspec/phpspec-code-coverage phpspec/phpspec
+
+ - name: PHPStan
+ uses: docker://oskarstark/phpstan-ga
+ env:
+ REQUIRE_DEV: false
+ with:
+ args: analyze --no-progress
+
+ php-cs-fixer:
+ name: PHP-CS-Fixer
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: PHP-CS-Fixer
+ uses: docker://oskarstark/php-cs-fixer-ga
+ with:
+ args: --dry-run --diff
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..145d475
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,125 @@
+name: tests
+
+on:
+ push:
+ branches:
+ - '*.x'
+ pull_request:
+
+jobs:
+ latest:
+ name: PHP ${{ matrix.php }} Latest
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ tools: composer:v2
+ coverage: none
+
+ - name: Emulate PHP 8.3
+ run: composer config platform.php 8.3.999
+ if: matrix.php == '8.4'
+
+ - name: Install PHP dependencies
+ run: composer update --prefer-dist --no-interaction --no-progress
+
+ - name: Execute tests
+ run: composer test
+
+ lowest:
+ name: PHP ${{ matrix.php }} Lowest
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php: ['7.1', '7.4', '8.2', '8.3']
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ tools: composer:v2
+ coverage: none
+
+ - name: Install dependencies
+ run: composer update --prefer-dist --prefer-stable --prefer-lowest --no-interaction --no-progress
+
+ - name: Execute tests
+ run: composer test
+
+ symfony:
+ name: Symfony ${{ matrix.symfony }} LTS
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ include:
+ - symfony: '4.4.*'
+ php-version: '7.1'
+ - symfony: '5.4.*'
+ php-version: '7.4'
+ - symfony: '6.4.*'
+ php-version: '8.2'
+ - symfony: '7.0.*'
+ php-version: '8.2'
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ tools: composer:v2
+ coverage: none
+
+ - name: Install dependencies
+ env:
+ SYMFONY_REQUIRE: ${{ matrix.symfony }}
+ run: |
+ composer global config --no-plugins allow-plugins.symfony/flex true
+ composer global require --no-progress --no-scripts --no-plugins symfony/flex
+ composer update --prefer-dist --no-interaction --prefer-stable --no-progress
+
+ - name: Execute tests
+ run: composer test
+
+ coverage:
+ name: Code Coverage
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 7.4
+ tools: composer:v2
+ coverage: xdebug
+
+ - name: Install dependencies
+ run: |
+ composer require "friends-of-phpspec/phpspec-code-coverage:^4.3.2" --no-interaction --no-update
+ composer update --prefer-dist --no-interaction --no-progress
+
+ - name: Execute tests
+ run: composer test-ci
+
+ - name: Upload coverage
+ run: |
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
diff --git a/.gitignore b/.gitignore
index 16b4a20..5874147 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,11 @@
+/.php-cs-fixer.php
+/.php-cs-fixer.cache
/behat.yml
/build/
/composer.lock
/phpspec.yml
/phpunit.xml
+/phpstan.neon
/vendor/
+
+.phpunit.result.cache
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..92bc748
--- /dev/null
+++ b/.php-cs-fixer.dist.php
@@ -0,0 +1,18 @@
+in(__DIR__.'/src')
+ ->in(__DIR__.'/tests')
+ ->name('*.php')
+;
+
+$config = new PhpCsFixer\Config();
+
+return $config
+ ->setRiskyAllowed(true)
+ ->setRules([
+ '@Symfony' => true,
+ 'single_line_throw' => false,
+ ])
+ ->setFinder($finder)
+;
diff --git a/.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,
+ 'phpdoc_to_comment' => false,
+ 'single_line_throw' => false,
+ ])
+ ->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
deleted file mode 100644
index 1e058db..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,68 +0,0 @@
-language: php
-
-sudo: false
-
-dist: trusty
-
-cache:
- directories:
- - $HOME/.composer/cache/files
-
-env:
- global:
- - TEST_COMMAND="composer test"
-
-branches:
- except:
- - /^analysis-.*$/
-
-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
- - php: 7.3
- env: COVERAGE=true TEST_COMMAND="composer test-ci" DEPENDENCIES="henrikbjorn/phpspec-code-coverage:^1.0"
-
- # 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;
-
-install:
- - cat composer.json
- # To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355
- - if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi
- - composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction
-
-script:
- - composer validate --strict --no-check-lock
- - $TEST_COMMAND
-
-after_success:
- - if [[ $COVERAGE = true ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi
- - if [[ $COVERAGE = true ]]; then php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml; fi
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21462c7..31ea0ea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,117 @@
# Change Log
+## 2.7.2 - 2024-09-24
+
+- Updated code to not raise warnings for nullable parameters in PHP 8.4.
+- Cleaned up PHPDoc comments.
+
+## 2.7.1 - 2023-11-30
+
+- Allow installation with Symfony 7.
+
+## 2.7.0 - 2023-05-17
+
+- Dropped `php-http/message-factory` from composer requirements as it is abandoned and this package does not actually use it.
+
+## 2.6.1 - 2023-04-14
+
+- Allow installation with http-message (PSR-7) version 2 in addition to version 1.
+- Support for PHP 8.2
+
+## 2.6.0 - 2022-09-29
+
+- [RedirectPlugin] Redirection of non GET/HEAD requests with a body now removes the body on follow-up requests, if the
+ HTTP method changes. To do this, the plugin needs to find a PSR-7 stream implementation. If none is found, you can
+ explicitly pass a PSR-17 StreamFactoryInterface in the `stream_factory` option.
+ To keep sending the body in all cases, set the `stream_factory` option to null explicitly.
+
+## 2.5.1 - 2022-09-29
+
+### Fixed
+
+- [RedirectPlugin] Fixed handling of redirection to different domain with default port
+- [RedirectPlugin] Fixed false positive circular detection in RedirectPlugin in cases when target location does not contain path
+
+## 2.5.0 - 2021-11-26
+
+### Added
+
+- Support for Symfony 6
+- Support for PHP 8.1
+
+### Changed
+
+- Dropped support for Symfony 2 and 3 - please keep using version 2.4.0 of this library if you can't update Symfony.
+
+## 2.4.0 - 2021-07-05
+
+### Added
+
+- `strict` option to `RedirectPlugin` to allow preserving the request method on redirections with status 300, 301 and 302.
+
+## 2.3.0 - 2020-07-21
+
+### Fixed
+
+- HttpMethodsClient with PSR RequestFactory
+- Bug in the cookie plugin with empty cookies
+- Bug when parsing null-valued date headers
+
+### Changed
+
+- Deprecation when constructing a HttpMethodsClient with PSR RequestFactory but without a StreamFactory
+
+## 2.2.1 - 2020-07-13
+
+### Fixed
+
+- Support for PHP 8
+- Plugin callable phpdoc
+
+## 2.2.0 - 2020-07-02
+
+### Added
+
+- Plugin client builder for making a `PluginClient`
+- Support for the PSR-17 request factory in `HttpMethodsClient`
+
+### Changed
+
+- Restored support for `symfony/options-resolver: ^2.6`
+- Consistent implementation of union type checking
+
+### Fixed
+
+- Memory leak when using the `PluginClient` with plugins
+
+## 2.1.0 - 2019-11-18
+
+### Added
+
+- Support Symfony 5
+
+## 2.0.0 - 2019-02-03
+
+### Changed
+
+- HttpClientRouter now throws a HttpClientNoMatchException instead of a RequestException if it can not find a client for the request.
+- RetryPlugin will only retry exceptions when there is no response, or a response in the 5xx HTTP code range.
+- RetryPlugin also retries when no exception is thrown if the responses has HTTP code in the 5xx range.
+ The callbacks for exception handling have been renamed and callbacks for response handling have been added.
+- Abstract method `HttpClientPool::chooseHttpClient()` has now an explicit return type (`Http\Client\Common\HttpClientPoolItem`)
+- Interface method `Plugin::handleRequest(...)` has now an explicit return type (`Http\Promise\Promise`)
+- Made classes final that are not intended to be extended.
+- 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.
+- All exceptions in `Http\Client\Common\Exception` are final.
+
+### Removed
+
+- Deprecated option `debug_plugins` has been removed from `PluginClient`
+- Deprecated options `decider` and `delay` have been removed from `RetryPlugin`, use `exception_decider` and `exception_delay` instead.
+
## 1.11.0 - 2021-07-11
### Changed
@@ -28,7 +140,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
@@ -59,10 +171,9 @@
- Decoder plugin will now remove header when there is no more encoding, instead of setting to an empty array
-
## 1.7.0 - 2017-11-30
-### Added
+### Added
- Symfony 4 support
@@ -82,12 +193,12 @@
### Changed
-- The `RetryPlugin` does now wait between retries. To disable/change this feature you must write something like:
-
+- The `RetryPlugin` does now wait between retries. To disable/change this feature you must write something like:
+
```php
-$plugin = new RetryPlugin(['delay' => function(RequestInterface $request, Exception $e, $retries) {
- return 0;
-});
+$plugin = new RetryPlugin(['delay' => function(RequestInterface $request, Exception $e, $retries) {
+ return 0;
+});
```
### Deprecated
diff --git a/README.md b/README.md
index 017bfce..822d4cf 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
[](https://github.com/php-http/client-common/releases)
[](LICENSE)
-[](https://travis-ci.org/php-http/client-common)
+[](https://github.com/php-http/client-common/actions/workflows/tests.yml)
[](https://scrutinizer-ci.com/g/php-http/client-common)
[](https://scrutinizer-ci.com/g/php-http/client-common)
[](https://packagist.org/packages/php-http/client-common)
diff --git a/composer.json b/composer.json
index cce1f4f..25afb92 100644
--- a/composer.json
+++ b/composer.json
@@ -11,17 +11,26 @@
}
],
"require": {
- "php": "^5.4 || ^7.0",
- "php-http/httplug": "^1.1",
- "php-http/message-factory": "^1.0",
+ "php": "^7.1 || ^8.0",
+ "php-http/httplug": "^2.0",
"php-http/message": "^1.6",
- "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0 || ^5.0"
+ "psr/http-client": "^1.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.0 || ^2.0",
+ "symfony/options-resolver": "~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0 || ^6.0 || ^7.0",
+ "symfony/polyfill-php80": "^1.17"
},
"require-dev": {
- "phpspec/phpspec": "^2.5 || ^3.4 || ^4.2",
- "guzzlehttp/psr7": "^1.4"
+ "doctrine/instantiator": "^1.1",
+ "guzzlehttp/psr7": "^1.4",
+ "nyholm/psr7": "^1.2",
+ "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1",
+ "phpspec/prophecy": "^1.10.2",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.33 || ^9.6.7"
},
"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 +40,23 @@
"Http\\Client\\Common\\": "src/"
}
},
+ "autoload-dev": {
+ "psr-4": {
+ "spec\\Http\\Client\\Common\\": "spec/",
+ "Tests\\Http\\Client\\Common\\": "tests/"
+ }
+ },
"scripts": {
- "test": "vendor/bin/phpspec run",
- "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml"
+ "test": [
+ "vendor/bin/phpspec run",
+ "vendor/bin/phpunit"
+ ],
+ "test-ci": [
+ "vendor/bin/phpspec run -c phpspec.ci.yml",
+ "vendor/bin/phpunit"
+ ]
},
- "extra": {
- "branch-alias": {
- "dev-master": "1.10.x-dev"
- }
+ "config": {
+ "sort-packages": true
}
}
diff --git a/phpspec.ci.yml b/phpspec.ci.yml
index 0838ef5..06a7469 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
+ FriendsOfPhpSpec\PhpSpec\CodeCoverage\CodeCoverageExtension: ~
code_coverage:
format: clover
output: build/coverage.xml
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 0000000..8886dea
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,80 @@
+parameters:
+ level: max
+ checkMissingIterableValueType: false
+ treatPhpDocTypesAsCertain: false
+ paths:
+ - src
+ ignoreErrors:
+ # Exception still thrown in PHP 8, not sure why phpstan complains
+ -
+ message: "#^Dead catch - UnexpectedValueException is never thrown in the try block\\.$#"
+ count: 2
+ path: src/BatchResult.php
+
+ -
+ message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addSuccess\\(\\) has no return type specified\\.$#"
+ count: 1
+ path: src/Plugin/Journal.php
+
+ -
+ message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addFailure\\(\\) has no return type specified\\.$#"
+ count: 1
+ path: src/Plugin/Journal.php
+
+ -
+ message: "#^Call to an undefined method Http\\\\Client\\\\HttpAsyncClient\\:\\:sendRequest\\(\\)\\.$#"
+ count: 1
+ path: src/PluginClient.php
+
+ -
+ message: "#^Method Http\\\\Client\\\\Common\\\\EmulatedHttpClient\\:\\:sendRequest\\(\\) should return Psr\\\\Http\\\\Message\\\\ResponseInterface but returns mixed\\.$#"
+ count: 1
+ path: src/EmulatedHttpClient.php
+
+ # we still support the obsolete RequestFactory for BC but do not require the package anymore
+ -
+ message: "#^Call to method createRequest\\(\\) on an unknown class Http\\\\Message\\\\RequestFactory\\.$#"
+ count: 1
+ path: src/HttpMethodsClient.php
+
+ -
+ message: "#^Class Http\\\\Message\\\\RequestFactory not found\\.$#"
+ count: 4
+ path: src/HttpMethodsClient.php
+
+ -
+ message: "#^Parameter \\$requestFactory of method Http\\\\Client\\\\Common\\\\HttpMethodsClient\\:\\:__construct\\(\\) has invalid type Http\\\\Message\\\\RequestFactory\\.$#"
+ count: 1
+ path: src/HttpMethodsClient.php
+
+ -
+ message: "#^Property Http\\\\Client\\\\Common\\\\HttpMethodsClient\\:\\:\\$requestFactory has unknown class Http\\\\Message\\\\RequestFactory as its type\\.$#"
+ count: 1
+ path: src/HttpMethodsClient.php
+
+ -
+ message: "#^Anonymous function should return Psr\\\\Http\\\\Message\\\\ResponseInterface but returns mixed\\.$#"
+ count: 1
+ path: src/Plugin/RedirectPlugin.php
+
+ # phpstan is confused by the optional dependencies. we check for existence first
+ -
+ message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\RedirectPlugin::guessStreamFactory\\(\\) should return Psr\\\\Http\\\\Message\\\\StreamFactoryInterface\\|null but returns Nyholm\\\\Psr7\\\\Factory\\\\Psr17Factory\\.$#"
+ count: 1
+ path: src/Plugin/RedirectPlugin.php
+
+ # phpstan is confused by the optional dependencies. we check for existence first
+ -
+ message: "#^Call to static method streamFor\\(\\) on an unknown class GuzzleHttp\\\\Psr7\\\\Utils\\.$#"
+ count: 1
+ path: src/Plugin/RedirectPlugin.php
+
+ -
+ message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\RetryPlugin\\:\\:retry\\(\\) should return Psr\\\\Http\\\\Message\\\\ResponseInterface but returns mixed\\.$#"
+ count: 1
+ path: src/Plugin/RetryPlugin.php
+
+ -
+ message: "#^Method Http\\\\Client\\\\Common\\\\PluginClient\\:\\:sendRequest\\(\\) should return Psr\\\\Http\\\\Message\\\\ResponseInterface but returns mixed\\.$#"
+ count: 2
+ path: src/PluginClient.php
diff --git a/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..7dcb261 100644
--- a/spec/BatchClientSpec.php
+++ b/spec/BatchClientSpec.php
@@ -2,31 +2,35 @@
namespace spec\Http\Client\Common;
+use Http\Client\Common\BatchClient;
+use Http\Client\Common\BatchResult;
+use Http\Client\Common\Exception\BatchException;
+use Http\Client\Exception\HttpException;
use Http\Client\HttpClient;
+use PhpSpec\ObjectBehavior;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
-use PhpSpec\ObjectBehavior;
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..775bb50 100644
--- a/spec/BatchResultSpec.php
+++ b/spec/BatchResultSpec.php
@@ -2,28 +2,29 @@
namespace spec\Http\Client\Common;
+use Http\Client\Common\BatchResult;
use Http\Client\Exception;
+use PhpSpec\ObjectBehavior;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
-use PhpSpec\ObjectBehavior;
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..d5d114a 100644
--- a/spec/EmulatedHttpAsyncClientSpec.php
+++ b/spec/EmulatedHttpAsyncClientSpec.php
@@ -2,51 +2,56 @@
namespace spec\Http\Client\Common;
+use Http\Client\Common\EmulatedHttpAsyncClient;
+use Http\Client\Exception\TransferException;
+use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
+use Http\Client\Promise\HttpFulfilledPromise;
+use Http\Client\Promise\HttpRejectedPromise;
+use PhpSpec\ObjectBehavior;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
-use PhpSpec\ObjectBehavior;
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..cc9f91e 100644
--- a/spec/EmulatedHttpClientSpec.php
+++ b/spec/EmulatedHttpClientSpec.php
@@ -2,37 +2,39 @@
namespace spec\Http\Client\Common;
+use Http\Client\Common\EmulatedHttpClient;
+use Http\Client\Exception;
use Http\Client\Exception\TransferException;
-use Http\Client\HttpClient;
use Http\Client\HttpAsyncClient;
+use Http\Client\HttpClient;
use Http\Promise\Promise;
+use PhpSpec\ObjectBehavior;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
-use PhpSpec\ObjectBehavior;
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..be72814 100644
--- a/spec/Exception/BatchExceptionSpec.php
+++ b/spec/Exception/BatchExceptionSpec.php
@@ -3,34 +3,35 @@
namespace spec\Http\Client\Common\Exception;
use Http\Client\Common\BatchResult;
+use Http\Client\Common\Exception\BatchException;
use Http\Client\Exception;
use PhpSpec\ObjectBehavior;
class BatchExceptionSpec extends ObjectBehavior
{
- function let()
+ public function let()
{
$batchResult = new BatchResult();
$this->beConstructedWith($batchResult);
}
- function it_is_initializable()
+ public function it_is_initializable()
{
- $this->shouldHaveType('Http\Client\Common\Exception\BatchException');
+ $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..ec88da7 100644
--- a/spec/FlexibleHttpClientSpec.php
+++ b/spec/FlexibleHttpClientSpec.php
@@ -2,43 +2,44 @@
namespace spec\Http\Client\Common;
+use Http\Client\Common\FlexibleHttpClient;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Http\Promise\Promise;
+use PhpSpec\ObjectBehavior;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
-use PhpSpec\ObjectBehavior;
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_type_error_if_invalid_client()
{
$this->beConstructedWith(null);
- $this->shouldThrow('\LogicException')->duringInstantiation();
+ $this->shouldThrow(\TypeError::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..537378f 100644
--- a/spec/HttpClientPoolItemSpec.php
+++ b/spec/HttpClientPool/HttpClientPoolItemSpec.php
@@ -1,8 +1,9 @@
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..367288d 100644
--- a/spec/HttpClientPool/LeastUsedClientPoolSpec.php
+++ b/spec/HttpClientPool/LeastUsedClientPoolSpec.php
@@ -2,11 +2,13 @@
namespace spec\Http\Client\Common\HttpClientPool;
-use Http\Client\Common\HttpClientPoolItem;
+use Http\Client\Common\Exception\HttpClientNotFoundException;
+use Http\Client\Common\HttpClientPool\HttpClientPoolItem;
+use Http\Client\Common\HttpClientPool\LeastUsedClientPool;
+use Http\Client\Exception\HttpException;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Http\Promise\Promise;
-use PhpSpec\Exception\Example\SkippingException;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\RequestInterface;
@@ -16,23 +18,23 @@ 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..5cb34c3 100644
--- a/spec/HttpClientPool/RandomClientPoolSpec.php
+++ b/spec/HttpClientPool/RandomClientPoolSpec.php
@@ -2,7 +2,10 @@
namespace spec\Http\Client\Common\HttpClientPool;
-use Http\Client\Common\HttpClientPoolItem;
+use Http\Client\Common\Exception\HttpClientNotFoundException;
+use Http\Client\Common\HttpClientPool\HttpClientPoolItem;
+use Http\Client\Common\HttpClientPool\RandomClientPool;
+use Http\Client\Exception\HttpException;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Http\Promise\Promise;
@@ -15,23 +18,23 @@ 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..5a272b2 100644
--- a/spec/HttpClientPool/RoundRobinClientPoolSpec.php
+++ b/spec/HttpClientPool/RoundRobinClientPoolSpec.php
@@ -2,7 +2,10 @@
namespace spec\Http\Client\Common\HttpClientPool;
-use Http\Client\Common\HttpClientPoolItem;
+use Http\Client\Common\Exception\HttpClientNotFoundException;
+use Http\Client\Common\HttpClientPool\HttpClientPoolItem;
+use Http\Client\Common\HttpClientPool\RoundRobinClientPool;
+use Http\Client\Exception\HttpException;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Http\Promise\Promise;
@@ -15,23 +18,23 @@ 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..db2f112 100644
--- a/spec/HttpClientRouterSpec.php
+++ b/spec/HttpClientRouterSpec.php
@@ -2,32 +2,40 @@
namespace spec\Http\Client\Common;
-use Http\Message\RequestMatcher;
+use Http\Client\Common\Exception\HttpClientNoMatchException;
+use Http\Client\Common\HttpClientRouter;
+use Http\Client\Common\HttpClientRouterInterface;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
+use Http\Message\RequestMatcher;
use Http\Promise\Promise;
+use PhpSpec\ObjectBehavior;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
-use PhpSpec\ObjectBehavior;
class HttpClientRouterSpec extends ObjectBehavior
{
- function it_is_initializable()
+ public function it_is_initializable()
+ {
+ $this->shouldHaveType(HttpClientRouter::class);
+ }
+
+ public function it_is_an_http_client_router()
{
- $this->shouldHaveType('Http\Client\Common\HttpClientRouter');
+ $this->shouldImplement(HttpClientRouterInterface::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_send_request(RequestMatcher $matcher, HttpClient $client, RequestInterface $request, ResponseInterface $response)
+ public function it_send_request(RequestMatcher $matcher, HttpClient $client, RequestInterface $request, ResponseInterface $response)
{
$this->addClient($client, $matcher);
$matcher->matches($request)->willReturn(true);
@@ -36,7 +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(HttpClientNoMatchException::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(HttpClientNoMatchException::class)->duringSendAsyncRequest($request);
}
}
diff --git a/spec/HttpMethodsClientSpec.php b/spec/HttpMethodsClientSpec.php
deleted file mode 100644
index 07c0b47..0000000
--- a/spec/HttpMethodsClientSpec.php
+++ /dev/null
@@ -1,116 +0,0 @@
-beAnInstanceOf(
- 'spec\Http\Client\Common\HttpMethodsClientStub', [
- $client,
- $messageFactory
- ]
- );
- }
-
- function it_sends_a_get_request()
- {
- $data = HttpMethodsClientStub::$requestData;
-
- $this->get($data['uri'], $data['headers'])->shouldReturn(true);
- }
-
- function it_sends_a_head_request()
- {
- $data = HttpMethodsClientStub::$requestData;
-
- $this->head($data['uri'], $data['headers'])->shouldReturn(true);
- }
-
- function it_sends_a_trace_request()
- {
- $data = HttpMethodsClientStub::$requestData;
-
- $this->trace($data['uri'], $data['headers'])->shouldReturn(true);
- }
-
- function it_sends_a_post_request()
- {
- $data = HttpMethodsClientStub::$requestData;
-
- $this->post($data['uri'], $data['headers'], $data['body'])->shouldReturn(true);
- }
-
- function it_sends_a_put_request()
- {
- $data = HttpMethodsClientStub::$requestData;
-
- $this->put($data['uri'], $data['headers'], $data['body'])->shouldReturn(true);
- }
-
- function it_sends_a_patch_request()
- {
- $data = HttpMethodsClientStub::$requestData;
-
- $this->patch($data['uri'], $data['headers'], $data['body'])->shouldReturn(true);
- }
-
- function it_sends_a_delete_request()
- {
- $data = HttpMethodsClientStub::$requestData;
-
- $this->delete($data['uri'], $data['headers'], $data['body'])->shouldReturn(true);
- }
-
- function it_sends_a_options_request()
- {
- $data = HttpMethodsClientStub::$requestData;
-
- $this->options($data['uri'], $data['headers'], $data['body'])->shouldReturn(true);
- }
-
- function it_sends_request_with_underlying_client(HttpClient $client, MessageFactory $messageFactory, RequestInterface $request, ResponseInterface $response)
- {
- $client->sendRequest($request)->shouldBeCalled()->willReturn($response);
-
- $this->beConstructedWith($client, $messageFactory);
- $this->sendRequest($request)->shouldReturn($response);
- }
-}
-
-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)
- {
- 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'];
- }
-}
diff --git a/spec/Plugin/AddHostPluginSpec.php b/spec/Plugin/AddHostPluginSpec.php
index caf4f21..e90ddfc 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 Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin\AddHostPlugin;
+use PhpSpec\ObjectBehavior;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
-use PhpSpec\ObjectBehavior;
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..4075622 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 Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin\AddPathPlugin;
+use PhpSpec\ObjectBehavior;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
-use PhpSpec\ObjectBehavior;
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..3191ade 100644
--- a/spec/Plugin/AuthenticationPluginSpec.php
+++ b/spec/Plugin/AuthenticationPluginSpec.php
@@ -2,34 +2,36 @@
namespace spec\Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin\AuthenticationPlugin;
use Http\Message\Authentication;
use Http\Promise\Promise;
-use Psr\Http\Message\RequestInterface;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
+use Psr\Http\Message\RequestInterface;
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..41d876b 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 Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin\BaseUriPlugin;
+use PhpSpec\ObjectBehavior;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
-use PhpSpec\ObjectBehavior;
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..39a8b71 100644
--- a/spec/Plugin/ContentLengthPluginSpec.php
+++ b/spec/Plugin/ContentLengthPluginSpec.php
@@ -2,37 +2,39 @@
namespace spec\Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin\ContentLengthPlugin;
use PhpSpec\Exception\Example\SkippingException;
-use Psr\Http\Message\RequestInterface;
-use Psr\Http\Message\StreamInterface;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\StreamInterface;
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..be5cd62 100644
--- a/spec/Plugin/CookiePluginSpec.php
+++ b/spec/Plugin/CookiePluginSpec.php
@@ -2,38 +2,41 @@
namespace spec\Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin\CookiePlugin;
+use Http\Client\Exception\TransferException;
use Http\Client\Promise\HttpFulfilledPromise;
+use Http\Client\Promise\HttpRejectedPromise;
use Http\Message\Cookie;
use Http\Message\CookieJar;
use Http\Promise\Promise;
+use PhpSpec\ObjectBehavior;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
-use PhpSpec\ObjectBehavior;
-use Prophecy\Argument;
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..c3731c8 100644
--- a/spec/Plugin/DecoderPluginSpec.php
+++ b/spec/Plugin/DecoderPluginSpec.php
@@ -2,35 +2,39 @@
namespace spec\Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin\DecoderPlugin;
use Http\Client\Promise\HttpFulfilledPromise;
-use Psr\Http\Message\RequestInterface;
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\StreamInterface;
+use Http\Message\Encoding\DecompressStream;
+use Http\Message\Encoding\GzipDecodeStream;
use PhpSpec\Exception\Example\SkippingException;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamInterface;
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..7861246 100644
--- a/spec/Plugin/ErrorPluginSpec.php
+++ b/spec/Plugin/ErrorPluginSpec.php
@@ -2,82 +2,87 @@
namespace spec\Http\Client\Common\Plugin;
+use Http\Client\Common\Exception\ClientErrorException;
+use Http\Client\Common\Exception\ServerErrorException;
+use Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin\ErrorPlugin;
use Http\Client\Promise\HttpFulfilledPromise;
-use Psr\Http\Message\RequestInterface;
-use Psr\Http\Message\ResponseInterface;
+use Http\Client\Promise\HttpRejectedPromise;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
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->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->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->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');
+ $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..cd8459b 100644
--- a/spec/Plugin/HistoryPluginSpec.php
+++ b/spec/Plugin/HistoryPluginSpec.php
@@ -2,35 +2,36 @@
namespace spec\Http\Client\Common\Plugin;
-use Http\Client\Exception\TransferException;
+use Http\Client\Common\Plugin;
use Http\Client\Common\Plugin\Journal;
+use Http\Client\Exception\TransferException;
use Http\Client\Promise\HttpFulfilledPromise;
use Http\Client\Promise\HttpRejectedPromise;
-use Psr\Http\Message\RequestInterface;
-use Psr\Http\Message\ResponseInterface;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
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..9bcfb7f 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,
@@ -32,7 +37,8 @@ function it_redirects_on_302(
ResponseInterface $finalResponse,
Promise $promise
) {
- $responseRedirect->getStatusCode()->willReturn('302');
+ $this->beConstructedWith(['stream_factory' => null]);
+ $responseRedirect->getStatusCode()->willReturn(302);
$responseRedirect->hasHeader('Location')->willReturn(true);
$responseRedirect->getHeaderLine('Location')->willReturn('/redirect');
@@ -48,14 +54,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 +70,24 @@ 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');
-
+ $this->beConstructedWith(['stream_factory' => null]);
$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 +95,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,
@@ -149,15 +155,19 @@ function it_replace_full_url(
ResponseInterface $finalResponse,
Promise $promise
) {
+ $this->beConstructedWith(['stream_factory' => null]);
$request->getUri()->willReturn($uri);
$uri->__toString()->willReturn('/original');
- $responseRedirect->getStatusCode()->willReturn('302');
+ $responseRedirect->getStatusCode()->willReturn(302);
$responseRedirect->hasHeader('Location')->willReturn(true);
$responseRedirect->getHeaderLine('Location')->willReturn('https://server.com:8000/redirect?query#fragment');
$request->getUri()->willReturn($uri);
$uri->withScheme('https')->willReturn($uriRedirect);
+ $uri->withPath('/redirect')->willReturn($uri);
+ $uri->withQuery('query')->willReturn($uri);
+ $uri->withFragment('fragment')->willReturn($uri);
$uriRedirect->withHost('server.com')->willReturn($uriRedirect);
$uriRedirect->withPort('8000')->willReturn($uriRedirect);
$uriRedirect->withPath('/redirect')->willReturn($uriRedirect);
@@ -171,13 +181,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 +199,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());
}
@@ -199,17 +209,17 @@ function it_throws_http_exception_on_no_location(RequestInterface $request, UriI
$request->getUri()->willReturn($uri);
$uri->__toString()->willReturn('/original');
- $responseRedirect->getStatusCode()->willReturn('302');
+ $responseRedirect->getStatusCode()->willReturn(302);
$responseRedirect->hasHeader('Location')->willReturn(false);
$promise = $this->handleRequest($request, $next, function () {});
- $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());
}
@@ -219,47 +229,47 @@ function it_throws_http_exception_on_invalid_location(RequestInterface $request,
$uri->__toString()->willReturn('/original');
$responseRedirect->getHeaderLine('Location')->willReturn('scheme:///invalid');
- $responseRedirect->getStatusCode()->willReturn('302');
+ $responseRedirect->getStatusCode()->willReturn(302);
$responseRedirect->hasHeader('Location')->willReturn(true);
$promise = $this->handleRequest($request, $next, function () {});
- $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());
}
};
$this->beConstructedWith(['preserve_header' => true, 'use_default_for_multiple' => false]);
- $responseRedirect->getStatusCode()->willReturn('300');
+ $responseRedirect->getStatusCode()->willReturn(300);
$promise = $this->handleRequest($request, $next, function () {});
- $promise->shouldReturnAnInstanceOf('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());
}
};
- $responseRedirect->getStatusCode()->willReturn('300');
+ $responseRedirect->getStatusCode()->willReturn(300);
$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,
@@ -268,10 +278,11 @@ function it_switch_method_for_302(
ResponseInterface $finalResponse,
Promise $promise
) {
+ $this->beConstructedWith(['stream_factory' => null]);
$request->getUri()->willReturn($uri);
$uri->__toString()->willReturn('/original');
- $responseRedirect->getStatusCode()->willReturn('302');
+ $responseRedirect->getStatusCode()->willReturn(302);
$responseRedirect->hasHeader('Location')->willReturn(true);
$responseRedirect->getHeaderLine('Location')->willReturn('/redirect');
@@ -281,20 +292,18 @@ function it_switch_method_for_302(
$uriRedirect->withQuery('')->willReturn($uriRedirect);
$request->withUri($uriRedirect)->willReturn($modifiedRequest);
- $modifiedRequest->getUri()->willReturn($uriRedirect);
-
$modifiedRequest->getUri()->willReturn($uriRedirect);
$uriRedirect->__toString()->willReturn('/redirect');
$modifiedRequest->getMethod()->willReturn('POST');
- $modifiedRequest->withMethod('GET')->willReturn($modifiedRequest);
+ $modifiedRequest->withMethod('GET')->shouldBeCalled()->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 +315,7 @@ function it_switch_method_for_302(
$this->handleRequest($request, $next, $first);
}
- function it_clears_headers(
+ public function it_does_not_switch_method_for_302_with_strict_option(
UriInterface $uri,
UriInterface $uriRedirect,
RequestInterface $request,
@@ -315,12 +324,12 @@ function it_clears_headers(
ResponseInterface $finalResponse,
Promise $promise
) {
- $this->beConstructedWith(['preserve_header' => ['Accept']]);
+ $this->beConstructedWith(['strict' => true]);
$request->getUri()->willReturn($uri);
$uri->__toString()->willReturn('/original');
- $responseRedirect->getStatusCode()->willReturn('302');
+ $responseRedirect->getStatusCode()->willReturn(302);
$responseRedirect->hasHeader('Location')->willReturn(true);
$responseRedirect->getHeaderLine('Location')->willReturn('/redirect');
@@ -330,21 +339,18 @@ function it_clears_headers(
$uriRedirect->withQuery('')->willReturn($uriRedirect);
$request->withUri($uriRedirect)->willReturn($modifiedRequest);
-
$modifiedRequest->getUri()->willReturn($uriRedirect);
$uriRedirect->__toString()->willReturn('/redirect');
- $modifiedRequest->getMethod()->willReturn('GET');
- $modifiedRequest->getHeaders()->willReturn(['Accept' => 'value', 'Cookie' => 'value']);
- $modifiedRequest->withoutHeader('Cookie')->willReturn($modifiedRequest);
- $modifiedRequest->getUri()->willReturn($uriRedirect);
+ $modifiedRequest->getMethod()->willReturn('POST');
+ $modifiedRequest->withMethod('GET')->shouldNotBeCalled();
- $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,114 +362,155 @@ 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->beAnInstanceOf('spec\Http\Client\Common\Plugin\RedirectPluginStubCircular');
- $this->beConstructedWith(spl_object_hash((object)$first));
+ public function it_clears_headers(
+ UriInterface $uri,
+ UriInterface $uriRedirect,
+ RequestInterface $request,
+ ResponseInterface $responseRedirect,
+ RequestInterface $modifiedRequest,
+ ResponseInterface $finalResponse,
+ Promise $promise
+ ) {
+ $this->beConstructedWith([
+ 'preserve_header' => ['Accept'],
+ 'stream_factory' => null,
+ ]);
$request->getUri()->willReturn($uri);
$uri->__toString()->willReturn('/original');
- $responseRedirect->getStatusCode()->willReturn('302');
+ $responseRedirect->getStatusCode()->willReturn(302);
$responseRedirect->hasHeader('Location')->willReturn(true);
$responseRedirect->getHeaderLine('Location')->willReturn('/redirect');
+ $request->getUri()->willReturn($uri);
$uri->withPath('/redirect')->willReturn($uriRedirect);
$uriRedirect->withFragment('')->willReturn($uriRedirect);
$uriRedirect->withQuery('')->willReturn($uriRedirect);
$request->withUri($uriRedirect)->willReturn($modifiedRequest);
+
$modifiedRequest->getUri()->willReturn($uriRedirect);
$uriRedirect->__toString()->willReturn('/redirect');
$modifiedRequest->getMethod()->willReturn('GET');
+ $modifiedRequest->getHeaders()->willReturn(['Accept' => 'value', 'Cookie' => 'value']);
+ $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());
}
};
- $promise = $this->handleRequest($request, $next, $first);
- $promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise');
- $promise->shouldThrow('Http\Client\Common\Exception\CircularRedirectionException')->duringWait();
+ $first = function (RequestInterface $receivedRequest) use ($modifiedRequest, $promise) {
+ if (Argument::is($modifiedRequest->getWrappedObject())->scoreArgument($receivedRequest)) {
+ return $promise->getWrappedObject();
+ }
+ };
+
+ $promise->getState()->willReturn(Promise::FULFILLED);
+ $promise->wait()->shouldBeCalled()->willReturn($finalResponse);
+
+ $this->handleRequest($request, $next, $first);
}
- function it_redirects_http_to_https(
- UriInterface $uri,
- UriInterface $uriRedirect,
+ /**
+ * 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 $responseRedirect,
- RequestInterface $modifiedRequest,
- ResponseInterface $finalResponse,
- Promise $promise
+ ResponseInterface $redirectResponse
) {
- $responseRedirect->getStatusCode()->willReturn('302');
- $responseRedirect->hasHeader('Location')->willReturn(true);
- $responseRedirect->getHeaderLine('Location')->willReturn('https://my-site.com/original');
+ $redirectResponse->getStatusCode()->willReturn(302);
+ $redirectResponse->hasHeader('Location')->willReturn(true);
+ $redirectResponse->getHeaderLine('Location')->willReturn('/redirect');
- $request->getUri()->willReturn($uri);
- $request->withUri($uriRedirect)->willReturn($modifiedRequest);
- $uri->__toString()->willReturn('http://my-site.com/original');
+ $next = function () use ($redirectResponse): Promise {
+ return new HttpFulfilledPromise($redirectResponse->getWrappedObject());
+ };
- $uri->withScheme('https')->willReturn($uriRedirect);
- $uriRedirect->withHost('my-site.com')->willReturn($uriRedirect);
- $uriRedirect->withPath('/original')->willReturn($uriRedirect);
- $uriRedirect->withFragment('')->willReturn($uriRedirect);
- $uriRedirect->withQuery('')->willReturn($uriRedirect);
- $uriRedirect->__toString()->willReturn('https://my-site.com/original');
+ $first = function () {
+ throw new \Exception('First should never be called');
+ };
- $modifiedRequest->getUri()->willReturn($uriRedirect);
- $modifiedRequest->getMethod()->willReturn('GET');
+ $request->getUri()->willReturn($redirectUri);
+ $redirectUri->__toString()->willReturn('/redirect');
- $next = function (RequestInterface $receivedRequest) use($request, $responseRedirect) {
- if (Argument::is($request->getWrappedObject())->scoreArgument($receivedRequest)) {
- return new HttpFulfilledPromise($responseRedirect->getWrappedObject());
- }
+ $redirectUri->withPath('/redirect')->willReturn($redirectUri);
+ $redirectUri->withFragment('')->willReturn($redirectUri);
+ $redirectUri->withQuery('')->willReturn($redirectUri);
+
+ $request->withUri($redirectUri)->willReturn($request);
+ $redirectUri->__toString()->willReturn('/redirect');
+ $request->getMethod()->willReturn('GET');
+
+ $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())
+ ;
};
- $first = function (RequestInterface $receivedRequest) use($modifiedRequest, $promise) {
- if (Argument::is($modifiedRequest->getWrappedObject())->scoreArgument($receivedRequest)) {
- return $promise->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);
};
- $promise->getState()->willReturn(Promise::FULFILLED);
- $promise->wait()->shouldBeCalled()->willReturn($finalResponse);
+ $request->getUri()->willReturn($uri);
+ $uri->__toString()->willReturn('/original');
- $finalPromise = $this->handleRequest($request, $next, $first);
- $finalPromise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise');
- $finalPromise->wait()->shouldReturn($finalResponse);
- }
-}
+ $modifiedRequest->getUri()->willReturn($redirectUri);
+ $redirectUri->__toString()->willReturn('/redirect');
-class RedirectPluginStub extends RedirectPlugin
-{
- public function __construct(UriInterface $uri, $storedUrl, $status, array $config = [])
- {
- parent::__construct($config);
+ $uri->withPath('/redirect')->willReturn($redirectUri);
+ $redirectUri->withFragment('')->willReturn($redirectUri);
+ $redirectUri->withQuery('')->willReturn($redirectUri);
- $this->redirectStorage[$storedUrl] = [
- 'uri' => $uri,
- 'status' => $status
- ];
- }
+ $redirectUri->withPath('/original')->willReturn($uri);
+ $uri->withFragment('')->willReturn($uri);
+ $uri->withQuery('')->willReturn($uri);
- public function hasStorage($url)
- {
- return isset($this->redirectStorage[$url]);
- }
-}
+ $request->withUri($redirectUri)->willReturn($modifiedRequest);
+ $request->getMethod()->willReturn('GET');
+ $modifiedRequest->withUri($uri)->willReturn($request);
+ $modifiedRequest->getMethod()->willReturn('GET');
-class RedirectPluginStubCircular extends RedirectPlugin
-{
- public function __construct($chainHash)
- {
- $this->circularDetection = [
- $chainHash => [
- '/redirect'
- ]
- ];
+ $promise = $this->handleRequest($request, $next, $first);
+ $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class);
+ $promise->shouldThrow(CircularRedirectionException::class)->duringWait();
}
}
diff --git a/spec/Plugin/RequestMatcherPluginSpec.php b/spec/Plugin/RequestMatcherPluginSpec.php
index 4fe9aea..246dc29 100644
--- a/spec/Plugin/RequestMatcherPluginSpec.php
+++ b/spec/Plugin/RequestMatcherPluginSpec.php
@@ -3,30 +3,31 @@
namespace spec\Http\Client\Common\Plugin;
use Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin\RequestMatcherPlugin;
use Http\Message\RequestMatcher;
use Http\Promise\Promise;
-use Psr\Http\Message\RequestInterface;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
+use Psr\Http\Message\RequestInterface;
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/RequestSeekableBodyPluginSpec.php b/spec/Plugin/RequestSeekableBodyPluginSpec.php
new file mode 100644
index 0000000..fbb5530
--- /dev/null
+++ b/spec/Plugin/RequestSeekableBodyPluginSpec.php
@@ -0,0 +1,46 @@
+shouldHaveType(RequestSeekableBodyPlugin::class);
+ }
+
+ public function it_is_a_plugin()
+ {
+ $this->shouldImplement(Plugin::class);
+ }
+
+ public function it_decorate_request_body_if_not_seekable(RequestInterface $request, StreamInterface $requestStream)
+ {
+ $request->getBody()->shouldBeCalled()->willReturn($requestStream);
+ $requestStream->isSeekable()->shouldBeCalled()->willReturn(false);
+ $requestStream->getSize()->willReturn(null);
+
+ $request->withBody(Argument::type(BufferedStream::class))->shouldBeCalled()->willReturn($request);
+
+ $this->handleRequest($request, PluginStub::next(), function () {});
+ }
+
+ public function it_does_not_decorate_request_body_if_seekable(RequestInterface $request, StreamInterface $requestStream)
+ {
+ $request->getBody()->shouldBeCalled()->willReturn($requestStream);
+ $requestStream->isSeekable()->shouldBeCalled()->willReturn(true);
+ $requestStream->getSize()->willReturn(null);
+
+ $request->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled();
+
+ $this->handleRequest($request, PluginStub::next(), function () {});
+ }
+}
diff --git a/spec/Plugin/ResponseSeekableBodyPluginSpec.php b/spec/Plugin/ResponseSeekableBodyPluginSpec.php
new file mode 100644
index 0000000..4f75027
--- /dev/null
+++ b/spec/Plugin/ResponseSeekableBodyPluginSpec.php
@@ -0,0 +1,56 @@
+shouldHaveType(ResponseSeekableBodyPlugin::class);
+ }
+
+ public function it_is_a_plugin()
+ {
+ $this->shouldImplement(Plugin::class);
+ }
+
+ public function it_decorate_response_body_if_not_seekable(ResponseInterface $response, StreamInterface $responseStream)
+ {
+ $next = function () use ($response) {
+ return new HttpFulfilledPromise($response->getWrappedObject());
+ };
+
+ $response->getBody()->shouldBeCalled()->willReturn($responseStream);
+ $responseStream->isSeekable()->shouldBeCalled()->willReturn(false);
+ $responseStream->getSize()->willReturn(null);
+
+ $response->withBody(Argument::type(BufferedStream::class))->shouldBeCalled()->willReturn($response);
+
+ $this->handleRequest(new Request('GET', '/'), $next, function () {});
+ }
+
+ public function it_does_not_decorate_response_body_if_seekable(ResponseInterface $response, StreamInterface $responseStream)
+ {
+ $next = function () use ($response) {
+ return new HttpFulfilledPromise($response->getWrappedObject());
+ };
+
+ $response->getBody()->shouldBeCalled()->willReturn($responseStream);
+ $responseStream->isSeekable()->shouldBeCalled()->willReturn(true);
+ $responseStream->getSize()->willReturn(null);
+
+ $response->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled();
+
+ $this->handleRequest(new Request('GET', '/'), $next, function () {});
+ }
+}
diff --git a/spec/Plugin/RetryPluginSpec.php b/spec/Plugin/RetryPluginSpec.php
index 4a985f3..4b749a7 100644
--- a/spec/Plugin/RetryPluginSpec.php
+++ b/spec/Plugin/RetryPluginSpec.php
@@ -2,95 +2,119 @@
namespace spec\Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin;
+use Http\Client\Common\Plugin\RetryPlugin;
use Http\Client\Exception;
use Http\Client\Promise\HttpFulfilledPromise;
use Http\Client\Promise\HttpRejectedPromise;
-use Psr\Http\Message\RequestInterface;
-use Psr\Http\Message\ResponseInterface;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
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,33 +128,41 @@ 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);
+ }
+
+ public function it_has_an_exponential_default_error_response_delay(RequestInterface $request, ResponseInterface $response)
+ {
+ $this->defaultErrorResponseDelay($request, $response, 0)->shouldBe(500000);
+ $this->defaultErrorResponseDelay($request, $response, 1)->shouldBe(1000000);
+ $this->defaultErrorResponseDelay($request, $response, 2)->shouldBe(2000000);
+ $this->defaultErrorResponseDelay($request, $response, 3)->shouldBe(4000000);
}
- function it_has_an_exponential_default_delay(RequestInterface $request, Exception\HttpException $exception)
+ public function it_has_an_exponential_default_exception_delay(RequestInterface $request, Exception\HttpException $exception)
{
- $this->defaultDelay($request, $exception, 0)->shouldBe(500000);
- $this->defaultDelay($request, $exception, 1)->shouldBe(1000000);
- $this->defaultDelay($request, $exception, 2)->shouldBe(2000000);
- $this->defaultDelay($request, $exception, 3)->shouldBe(4000000);
+ $this->defaultExceptionDelay($request, $exception, 0)->shouldBe(500000);
+ $this->defaultExceptionDelay($request, $exception, 1)->shouldBe(1000000);
+ $this->defaultExceptionDelay($request, $exception, 2)->shouldBe(2000000);
+ $this->defaultExceptionDelay($request, $exception, 3)->shouldBe(4000000);
}
}
diff --git a/spec/PluginClientFactorySpec.php b/spec/PluginClientFactorySpec.php
index 1f8d9e8..6b5868d 100644
--- a/spec/PluginClientFactorySpec.php
+++ b/spec/PluginClientFactorySpec.php
@@ -2,24 +2,26 @@
namespace spec\Http\Client\Common;
+use Http\Client\Common\PluginClient;
+use Http\Client\Common\PluginClientFactory;
use Http\Client\HttpClient;
use PhpSpec\ObjectBehavior;
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..fb6b153 100644
--- a/spec/PluginClientSpec.php
+++ b/spec/PluginClientSpec.php
@@ -2,46 +2,47 @@
namespace spec\Http\Client\Common;
+use Http\Client\Common\Exception\LoopException;
+use Http\Client\Common\Plugin;
+use Http\Client\Common\PluginClient;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
-use Http\Client\Common\FlexibleHttpClient;
-use Http\Client\Common\Plugin;
use Http\Promise\Promise;
+use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
-use PhpSpec\ObjectBehavior;
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..51f13ff 100644
--- a/src/BatchClient.php
+++ b/src/BatchClient.php
@@ -1,70 +1,34 @@
- */
-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)
- {
- 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();
foreach ($requests as $request) {
try {
- $response = $this->sendRequest($request);
+ $response = $this->client->sendRequest($request);
$batchResult = $batchResult->addResponse($request, $response);
- } catch (Exception $e) {
+ } catch (ClientExceptionInterface $e) {
$batchResult = $batchResult->addException($request, $e);
}
}
diff --git a/src/BatchClientInterface.php b/src/BatchClientInterface.php
new file mode 100644
index 0000000..d42eb2d
--- /dev/null
+++ b/src/BatchClientInterface.php
@@ -0,0 +1,34 @@
+
+ */
+interface BatchClientInterface
+{
+ /**
+ * 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[] $requests 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..ccaf83c 100644
--- a/src/BatchResult.php
+++ b/src/BatchResult.php
@@ -1,8 +1,10 @@
*/
private $responses;
/**
- * @var \SplObjectStorage
+ * @var \SplObjectStorage
*/
private $exceptions;
@@ -31,10 +33,8 @@ public function __construct()
/**
* Checks if there are any successful responses at all.
- *
- * @return bool
*/
- public function hasResponses()
+ public function hasResponses(): bool
{
return $this->responses->count() > 0;
}
@@ -44,7 +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,9 @@ 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 +80,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 +92,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;
}
@@ -114,9 +101,9 @@ public function hasExceptions()
/**
* Returns all exceptions for the unsuccessful requests.
*
- * @return Exception[]
+ * @return ClientExceptionInterface[]
*/
- public function getExceptions()
+ public function getExceptions(): array
{
$exceptions = [];
@@ -129,12 +116,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 +125,9 @@ 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): ClientExceptionInterface
{
try {
return $this->exceptions[$request];
@@ -160,12 +139,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, ClientExceptionInterface $exception): self
{
$new = clone $this;
$new->exceptions->attach($request, $exception);
diff --git a/src/Deferred.php b/src/Deferred.php
index 075a30e..effabb2 100644
--- a/src/Deferred.php
+++ b/src/Deferred.php
@@ -1,26 +1,46 @@
onRejectedCallbacks = [];
}
- /**
- * {@inheritdoc}
- */
- public function then(callable $onFulfilled = null, callable $onRejected = null)
+ public function then(?callable $onFulfilled = null, ?callable $onRejected = null): Promise
{
$deferred = new self($this->waitCallback);
@@ -44,12 +61,12 @@ public function then(callable $onFulfilled = null, callable $onRejected = null)
$response = $onFulfilled($response);
}
$deferred->resolve($response);
- } catch (Exception $exception) {
+ } catch (ClientExceptionInterface $exception) {
$deferred->reject($exception);
}
};
- $this->onRejectedCallbacks[] = function (Exception $exception) use ($onRejected, $deferred) {
+ $this->onRejectedCallbacks[] = function (ClientExceptionInterface $exception) use ($onRejected, $deferred) {
try {
if (null !== $onRejected) {
$response = $onRejected($exception);
@@ -58,7 +75,7 @@ public function then(callable $onFulfilled = null, callable $onRejected = null)
return;
}
$deferred->reject($exception);
- } catch (Exception $newException) {
+ } catch (ClientExceptionInterface $newException) {
$deferred->reject($newException);
}
};
@@ -66,10 +83,7 @@ public function then(callable $onFulfilled = null, callable $onRejected = null)
return $deferred;
}
- /**
- * {@inheritdoc}
- */
- public function getState()
+ public function getState(): string
{
return $this->state;
}
@@ -77,14 +91,14 @@ public function getState()
/**
* Resolve this deferred with a Response.
*/
- public function resolve(ResponseInterface $response)
+ public function resolve(ResponseInterface $response): void
{
- if (self::PENDING !== $this->state) {
+ if (Promise::PENDING !== $this->state) {
return;
}
$this->value = $response;
- $this->state = self::FULFILLED;
+ $this->state = Promise::FULFILLED;
foreach ($this->onFulfilledCallbacks as $onFulfilledCallback) {
$onFulfilledCallback($response);
@@ -94,38 +108,39 @@ public function resolve(ResponseInterface $response)
/**
* Reject this deferred with an Exception.
*/
- public function reject(Exception $exception)
+ public function reject(ClientExceptionInterface $exception): void
{
- if (self::PENDING !== $this->state) {
+ if (Promise::PENDING !== $this->state) {
return;
}
$this->failure = $exception;
- $this->state = self::REJECTED;
+ $this->state = Promise::REJECTED;
foreach ($this->onRejectedCallbacks as $onRejectedCallback) {
$onRejectedCallback($exception);
}
}
- /**
- * {@inheritdoc}
- */
public function wait($unwrap = true)
{
- if (self::PENDING === $this->state) {
+ if (Promise::PENDING === $this->state) {
$callback = $this->waitCallback;
$callback();
}
if (!$unwrap) {
- return;
+ return null;
}
- if (self::FULFILLED === $this->state) {
+ if (Promise::FULFILLED === $this->state) {
return $this->value;
}
+ if (null === $this->failure) {
+ throw new \RuntimeException('Internal Error: Promise is not fulfilled but has no exception stored');
+ }
+
throw $this->failure;
}
}
diff --git a/src/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..a9cb08c 100644
--- a/src/Exception/BatchException.php
+++ b/src/Exception/BatchException.php
@@ -1,9 +1,11 @@
result = $result;
+ parent::__construct();
}
/**
* Returns the BatchResult that contains all responses and exceptions.
- *
- * @return BatchResult
*/
- public function getResult()
+ public function getResult(): BatchResult
{
return $this->result;
}
diff --git a/src/Exception/CircularRedirectionException.php b/src/Exception/CircularRedirectionException.php
index 73ec521..9db927c 100644
--- a/src/Exception/CircularRedirectionException.php
+++ b/src/Exception/CircularRedirectionException.php
@@ -1,5 +1,7 @@
*/
-class CircularRedirectionException extends HttpException
+final class CircularRedirectionException extends HttpException
{
}
diff --git a/src/Exception/ClientErrorException.php b/src/Exception/ClientErrorException.php
index b1f6cc8..c657a3f 100644
--- a/src/Exception/ClientErrorException.php
+++ b/src/Exception/ClientErrorException.php
@@ -1,5 +1,7 @@
*/
-class ClientErrorException extends HttpException
+final class ClientErrorException extends HttpException
{
}
diff --git a/src/Exception/HttpClientNoMatchException.php b/src/Exception/HttpClientNoMatchException.php
new file mode 100644
index 0000000..83037d3
--- /dev/null
+++ b/src/Exception/HttpClientNoMatchException.php
@@ -0,0 +1,33 @@
+
+ */
+final class HttpClientNoMatchException extends TransferException
+{
+ /**
+ * @var RequestInterface
+ */
+ private $request;
+
+ public function __construct(string $message, RequestInterface $request, ?\Exception $previous = null)
+ {
+ $this->request = $request;
+
+ parent::__construct($message, 0, $previous);
+ }
+
+ public function getRequest(): RequestInterface
+ {
+ return $this->request;
+ }
+}
diff --git a/src/Exception/HttpClientNotFoundException.php b/src/Exception/HttpClientNotFoundException.php
index 5d33f98..509daa5 100644
--- a/src/Exception/HttpClientNotFoundException.php
+++ b/src/Exception/HttpClientNotFoundException.php
@@ -1,5 +1,7 @@
*/
-class HttpClientNotFoundException extends TransferException
+final class HttpClientNotFoundException extends TransferException
{
}
diff --git a/src/Exception/LoopException.php b/src/Exception/LoopException.php
index e834124..f4e173f 100644
--- a/src/Exception/LoopException.php
+++ b/src/Exception/LoopException.php
@@ -1,5 +1,7 @@
*/
-class LoopException extends RequestException
+final class LoopException extends RequestException
{
}
diff --git a/src/Exception/MultipleRedirectionException.php b/src/Exception/MultipleRedirectionException.php
index ae514cd..bf6c9f7 100644
--- a/src/Exception/MultipleRedirectionException.php
+++ b/src/Exception/MultipleRedirectionException.php
@@ -1,5 +1,7 @@
*/
-class MultipleRedirectionException extends HttpException
+final class MultipleRedirectionException extends HttpException
{
}
diff --git a/src/Exception/ServerErrorException.php b/src/Exception/ServerErrorException.php
index 665d724..774b97f 100644
--- a/src/Exception/ServerErrorException.php
+++ b/src/Exception/ServerErrorException.php
@@ -1,5 +1,7 @@
*/
-class ServerErrorException extends HttpException
+final class ServerErrorException extends HttpException
{
}
diff --git a/src/FlexibleHttpClient.php b/src/FlexibleHttpClient.php
index d0a6e88..c1e327f 100644
--- a/src/FlexibleHttpClient.php
+++ b/src/FlexibleHttpClient.php
@@ -1,5 +1,7 @@
httpClient = $client;
- $this->httpAsyncClient = $client;
-
- if (!($this->httpClient instanceof HttpClient) && !($client instanceof ClientInterface)) {
- $this->httpClient = new EmulatedHttpClient($this->httpClient);
- }
-
- if (!($this->httpAsyncClient instanceof HttpAsyncClient)) {
- $this->httpAsyncClient = new EmulatedHttpAsyncClient($this->httpAsyncClient);
- }
+ $this->httpClient = $client instanceof ClientInterface ? $client : new EmulatedHttpClient($client);
+ $this->httpAsyncClient = $client instanceof HttpAsyncClient ? $client : new EmulatedHttpAsyncClient($client);
}
}
diff --git a/src/HttpAsyncClientDecorator.php b/src/HttpAsyncClientDecorator.php
index 6eb576c..91ff5af 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..7c40b50 100644
--- a/src/HttpClientEmulator.php
+++ b/src/HttpClientEmulator.php
@@ -1,8 +1,11 @@
sendAsyncRequest($request);
@@ -24,8 +25,6 @@ public function sendRequest(RequestInterface $request)
}
/**
- * {@inheritdoc}
- *
* @see HttpAsyncClient::sendAsyncRequest
*/
abstract public function sendAsyncRequest(RequestInterface $request);
diff --git a/src/HttpClientPool.php b/src/HttpClientPool.php
index c51d044..24ab421 100644
--- a/src/HttpClientPool.php
+++ b/src/HttpClientPool.php
@@ -1,60 +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): void;
}
diff --git a/src/HttpClientPool/HttpClientPool.php b/src/HttpClientPool/HttpClientPool.php
new file mode 100644
index 0000000..d7c2dbd
--- /dev/null
+++ b/src/HttpClientPool/HttpClientPool.php
@@ -0,0 +1,64 @@
+clientPool[] = $client;
+ }
+
+ /**
+ * Return an http client given a specific strategy.
+ *
+ * @return HttpClientPoolItem Return a http client that can do both sync or async
+ *
+ * @throws HttpClientNotFoundException When no http client has been found into the pool
+ */
+ abstract protected function chooseHttpClient(): HttpClientPoolItem;
+
+ public function sendAsyncRequest(RequestInterface $request)
+ {
+ return $this->chooseHttpClient()->sendAsyncRequest($request);
+ }
+
+ public function sendRequest(RequestInterface $request): ResponseInterface
+ {
+ return $this->chooseHttpClient()->sendRequest($request);
+ }
+}
diff --git a/src/HttpClientPoolItem.php b/src/HttpClientPool/HttpClientPoolItem.php
similarity index 58%
rename from src/HttpClientPoolItem.php
rename to src/HttpClientPool/HttpClientPoolItem.php
index 5c79137..1bf4381 100644
--- a/src/HttpClientPoolItem.php
+++ b/src/HttpClientPool/HttpClientPoolItem.php
@@ -1,18 +1,29 @@
*/
@@ -29,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;
@@ -39,19 +54,22 @@ class HttpClientPoolItem implements HttpClient, HttpAsyncClient
private $client;
/**
- * @param HttpClient|HttpAsyncClient|ClientInterface $client
- * @param null|int $reenableAfter Number of seconds after this client is reenable
+ * @param ClientInterface|HttpAsyncClient $client
+ * @param int|null $reenableAfter Number of seconds until this client is enabled again after an error
*/
- public function __construct($client, $reenableAfter = null)
+ public function __construct($client, ?int $reenableAfter = null)
{
+ if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) {
+ throw new \TypeError(
+ sprintf('%s::__construct(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client))
+ );
+ }
+
$this->client = new FlexibleHttpClient($client);
$this->reenableAfter = $reenableAfter;
}
- /**
- * {@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);
@@ -71,9 +89,6 @@ public function sendRequest(RequestInterface $request)
return $response;
}
- /**
- * {@inheritdoc}
- */
public function sendAsyncRequest(RequestInterface $request)
{
if ($this->isDisabled()) {
@@ -97,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;
@@ -120,35 +130,21 @@ 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.
*/
- private function incrementRequestCount()
+ private function incrementRequestCount(): void
{
++$this->sendingRequestCount;
}
@@ -156,7 +152,7 @@ private function incrementRequestCount()
/**
* Decrement the request count.
*/
- private function decrementRequestCount()
+ private function decrementRequestCount(): void
{
--$this->sendingRequestCount;
}
@@ -164,7 +160,7 @@ private function decrementRequestCount()
/**
* Enable the current client.
*/
- private function enable()
+ private function enable(): void
{
$this->disabledAt = null;
}
@@ -172,7 +168,7 @@ private function enable()
/**
* Disable the current client.
*/
- private function disable()
+ private function disable(): void
{
$this->disabledAt = new \DateTime('now');
}
diff --git a/src/HttpClientPool/LeastUsedClientPool.php b/src/HttpClientPool/LeastUsedClientPool.php
index 6299cce..bfa1cd0 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..8ef535f 100644
--- a/src/HttpClientPool/RandomClientPool.php
+++ b/src/HttpClientPool/RandomClientPool.php
@@ -1,22 +1,19 @@
*/
final class RandomClientPool extends HttpClientPool
{
- /**
- * {@inheritdoc}
- */
- protected function chooseHttpClient()
+ protected function chooseHttpClient(): HttpClientPoolItem
{
$clientPool = array_filter($this->clientPool, function (HttpClientPoolItem $clientPoolItem) {
return !$clientPoolItem->isDisabled();
diff --git a/src/HttpClientPool/RoundRobinClientPool.php b/src/HttpClientPool/RoundRobinClientPool.php
index 8d8e40a..a908653 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 8f897d2..42c6907 100644
--- a/src/HttpClientRouter.php
+++ b/src/HttpClientRouter.php
@@ -1,54 +1,49 @@
*/
-final class HttpClientRouter implements HttpClient, HttpAsyncClient
+final class HttpClientRouter implements HttpClientRouterInterface
{
/**
- * @var array
+ * @var (array{matcher: RequestMatcher, client: FlexibleHttpClient})[]
*/
private $clients = [];
- /**
- * {@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);
}
- /**
- * {@inheritdoc}
- */
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|ClientInterface $client
- * @param RequestMatcher $requestMatcher
+ * @param ClientInterface|HttpAsyncClient $client
*/
- public function addClient($client, RequestMatcher $requestMatcher)
+ public function addClient($client, RequestMatcher $requestMatcher): void
{
+ if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) {
+ throw new \TypeError(
+ sprintf('%s::addClient(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client))
+ );
+ }
+
$this->clients[] = [
'matcher' => $requestMatcher,
'client' => new FlexibleHttpClient($client),
@@ -57,12 +52,8 @@ public function addClient($client, RequestMatcher $requestMatcher)
/**
* Choose an HTTP client given a specific request.
- *
- * @param RequestInterface $request
- *
- * @return HttpClient|HttpAsyncClient|ClientInterface
*/
- protected function chooseHttpClient(RequestInterface $request)
+ private function chooseHttpClient(RequestInterface $request): FlexibleHttpClient
{
foreach ($this->clients as $client) {
if ($client['matcher']->matches($request)) {
@@ -70,6 +61,6 @@ protected function chooseHttpClient(RequestInterface $request)
}
}
- throw new RequestException('No client found for the specified request', $request);
+ throw new HttpClientNoMatchException('No client found for the specified request', $request);
}
}
diff --git a/src/HttpClientRouterInterface.php b/src/HttpClientRouterInterface.php
new file mode 100644
index 0000000..ae012cf
--- /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): void;
+}
diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php
index c462c10..95fee3b 100644
--- a/src/HttpMethodsClient.php
+++ b/src/HttpMethodsClient.php
@@ -1,209 +1,149 @@
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;
/**
- * @var RequestFactory
+ * @var RequestFactory|RequestFactoryInterface
*/
private $requestFactory;
/**
- * @param HttpClient|ClientInterface $httpClient The client to send requests with
- * @param RequestFactory $requestFactory The message factory to create requests
+ * @var StreamFactoryInterface|null
+ */
+ private $streamFactory;
+
+ /**
+ * @param RequestFactory|RequestFactoryInterface $requestFactory
*/
- public function __construct($httpClient, RequestFactory $requestFactory)
+ public function __construct(ClientInterface $httpClient, $requestFactory, ?StreamFactoryInterface $streamFactory = null)
{
- if (!($httpClient instanceof HttpClient) && !($httpClient instanceof ClientInterface)) {
- throw new \LogicException('Client must be an instance of Http\\Client\\HttpClient or Psr\\Http\\Client\\ClientInterface');
+ if (!$requestFactory instanceof RequestFactory && !$requestFactory instanceof RequestFactoryInterface) {
+ throw new \TypeError(
+ sprintf('%s::__construct(): Argument #2 ($requestFactory) must be of type %s|%s, %s given', self::class, RequestFactory::class, RequestFactoryInterface::class, get_debug_type($requestFactory))
+ );
+ }
+
+ if (!$requestFactory instanceof RequestFactory && null === $streamFactory) {
+ @trigger_error(sprintf('Passing a %s without a %s to %s::__construct() is deprecated as of version 2.3 and will be disallowed in version 3.0. A stream factory is required to create a request with a non-empty string body.', RequestFactoryInterface::class, StreamFactoryInterface::class, self::class));
}
$this->httpClient = $httpClient;
$this->requestFactory = $requestFactory;
+ $this->streamFactory = $streamFactory;
}
- /**
- * 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);
}
+ public function send(string $method, $uri, array $headers = [], $body = null): ResponseInterface
+ {
+ if (!is_string($uri) && !$uri instanceof UriInterface) {
+ throw new \TypeError(
+ sprintf('%s::send(): Argument #2 ($uri) must be of type string|%s, %s given', self::class, UriInterface::class, get_debug_type($uri))
+ );
+ }
+
+ if (!is_string($body) && !$body instanceof StreamInterface && null !== $body) {
+ throw new \TypeError(
+ sprintf('%s::send(): Argument #4 ($body) must be of type string|%s|null, %s given', self::class, StreamInterface::class, get_debug_type($body))
+ );
+ }
+
+ return $this->sendRequest(
+ self::createRequest($method, $uri, $headers, $body)
+ );
+ }
+
/**
- * 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)
+ private function createRequest(string $method, $uri, array $headers = [], $body = null): RequestInterface
{
- return $this->sendRequest($this->requestFactory->createRequest(
- $method,
- $uri,
- $headers,
- $body
- ));
+ if ($this->requestFactory instanceof RequestFactory) {
+ return $this->requestFactory->createRequest(
+ $method,
+ $uri,
+ $headers,
+ $body
+ );
+ }
+
+ $request = $this->requestFactory->createRequest($method, $uri);
+
+ foreach ($headers as $key => $value) {
+ $request = $request->withHeader($key, $value);
+ }
+
+ if (null !== $body && '' !== $body) {
+ if (null === $this->streamFactory) {
+ throw new \RuntimeException('Cannot create request: A stream factory is required to create a request with a non-empty string body.');
+ }
+
+ $request = $request->withBody(
+ is_string($body) ? $this->streamFactory->createStream($body) : $body
+ );
+ }
+
+ return $request;
}
- /**
- * 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..bc0829a
--- /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(string $method, $uri, array $headers = [], $body = null): ResponseInterface;
+}
diff --git a/src/Plugin.php b/src/Plugin.php
index 89a2a62..99898b9 100644
--- a/src/Plugin.php
+++ b/src/Plugin.php
@@ -1,5 +1,7 @@
replace = $options['replace'];
}
- /**
- * {@inheritdoc}
- */
- public function handleRequest(RequestInterface $request, callable $next, callable $first)
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
{
if ($this->replace || '' === $request->getUri()->getHost()) {
$uri = $request->getUri()
@@ -64,10 +63,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
return $next($request);
}
- /**
- * @param OptionsResolver $resolver
- */
- private function configureOptions(OptionsResolver $resolver)
+ private function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'replace' => false,
diff --git a/src/Plugin/AddPathPlugin.php b/src/Plugin/AddPathPlugin.php
index 1d4762e..87fcdce 100644
--- a/src/Plugin/AddPathPlugin.php
+++ b/src/Plugin/AddPathPlugin.php
@@ -1,8 +1,11 @@
getPath()) {
@@ -69,7 +62,7 @@ public function __construct(UriInterface $uri)
*
* {@inheritdoc}
*/
- public function handleRequest(RequestInterface $request, callable $next, callable $first)
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
{
$prepend = $this->uri->getPath();
$path = $request->getUri()->getPath();
@@ -77,7 +70,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
if (substr($path, 0, strlen($prepend)) !== $prepend) {
$request = $request->withUri($request->getUri()
->withPath($prepend.$path)
- );
+ );
}
return $next($request);
diff --git a/src/Plugin/AuthenticationPlugin.php b/src/Plugin/AuthenticationPlugin.php
index 194712f..2336062 100644
--- a/src/Plugin/AuthenticationPlugin.php
+++ b/src/Plugin/AuthenticationPlugin.php
@@ -1,9 +1,12 @@
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..c361683 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..ad69ff3 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..e4844c4 100644
--- a/src/Plugin/ContentTypePlugin.php
+++ b/src/Plugin/ContentTypePlugin.php
@@ -1,8 +1,11 @@
sizeLimit = $options['size_limit'];
}
- /**
- * {@inheritdoc}
- */
- public function handleRequest(RequestInterface $request, callable $next, callable $first)
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
{
if (!$request->hasHeader('Content-Type')) {
$stream = $request->getBody();
@@ -91,13 +91,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());
@@ -105,19 +103,17 @@ private function isJson($stream)
return JSON_ERROR_NONE === json_last_error();
}
- /**
- * @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..23c8593 100644
--- a/src/Plugin/CookiePlugin.php
+++ b/src/Plugin/CookiePlugin.php
@@ -1,5 +1,7 @@
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 +88,14 @@ 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): ?Cookie
{
- $parts = array_map('trim', explode(';', $setCookie));
+ $parts = array_map('trim', explode(';', $setCookieHeader));
- if (empty($parts) || !strpos($parts[0], '=')) {
- return;
+ if ('' === $parts[0] || false === strpos($parts[0], '=')) {
+ return null;
}
list($name, $cookieValue) = $this->createValueKey(array_shift($parts));
@@ -122,7 +114,7 @@ private function createCookie(RequestInterface $request, $setCookie)
switch (strtolower($key)) {
case 'expires':
try {
- $expires = CookieUtil::parseDate($value);
+ $expires = CookieUtil::parseDate((string) $value);
} catch (UnexpectedValueException $e) {
throw new TransferException(
sprintf(
@@ -170,15 +162,15 @@ private function createCookie(RequestInterface $request, $setCookie)
/**
* Separates key/value pair from cookie.
*
- * @param $part
+ * @param string $part A single cookie value in format key=value
*
- * @return array
+ * @return array{0:string, 1:string|null}
*/
- private function createValueKey($part)
+ private function createValueKey(string $part): array
{
$parts = explode('=', $part, 2);
$key = trim($parts[0]);
- $value = isset($parts[1]) ? trim($parts[1]) : true;
+ $value = isset($parts[1]) ? trim($parts[1]) : null;
return [$key, $value];
}
diff --git a/src/Plugin/DecoderPlugin.php b/src/Plugin/DecoderPlugin.php
index 0239d40..3e781aa 100644
--- a/src/Plugin/DecoderPlugin.php
+++ b/src/Plugin/DecoderPlugin.php
@@ -1,9 +1,12 @@
useContentEncoding = $options['use_content_encoding'];
}
- /**
- * {@inheritdoc}
- */
- public function handleRequest(RequestInterface $request, callable $next, callable $first)
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
{
$encodings = extension_loaded('zlib') ? ['gzip', 'deflate'] : ['identity'];
@@ -65,12 +65,8 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
/**
* Decode a response body given its Transfer-Encoding or Content-Encoding value.
- *
- * @param ResponseInterface $response Response to decode
- *
- * @return ResponseInterface New response decoded
*/
- private function decodeResponse(ResponseInterface $response)
+ private function decodeResponse(ResponseInterface $response): ResponseInterface
{
$response = $this->decodeOnEncodingHeader('Transfer-Encoding', $response);
@@ -83,13 +79,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 +111,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..712e28a 100644
--- a/src/Plugin/ErrorPlugin.php
+++ b/src/Plugin/ErrorPlugin.php
@@ -1,10 +1,13 @@
*/
final class ErrorPlugin implements Plugin
@@ -26,10 +37,10 @@ final class ErrorPlugin implements Plugin
private $onlyServerException;
/**
- * @param array $config {
+ * @param array{'only_server_exception'?: bool} $config
*
- * @var bool only_server_exception Whether this plugin should only throw 5XX Exceptions (default to false).
- * }
+ * Configuration options:
+ * - only_server_exception: Whether this plugin should only throw 5XX Exceptions (default to false)
*/
public function __construct(array $config = [])
{
@@ -43,10 +54,7 @@ public function __construct(array $config = [])
$this->onlyServerException = $options['only_server_exception'];
}
- /**
- * {@inheritdoc}
- */
- public function handleRequest(RequestInterface $request, callable $next, callable $first)
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
{
$promise = $next($request);
@@ -61,12 +69,12 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
* @param RequestInterface $request Request of the call
* @param ResponseInterface $response Response of the call
*
+ * @return ResponseInterface If status code is not in 4xx or 5xx return response
+ *
* @throws ClientErrorException If response status code is a 4xx
* @throws ServerErrorException If response status code is a 5xx
- *
- * @return ResponseInterface If status code is not in 4xx or 5xx return response
*/
- protected function transformResponseToException(RequestInterface $request, ResponseInterface $response)
+ private function transformResponseToException(RequestInterface $request, ResponseInterface $response): ResponseInterface
{
if (!$this->onlyServerException && $response->getStatusCode() >= 400 && $response->getStatusCode() < 500) {
throw new ClientErrorException($response->getReasonPhrase(), $request, $response);
diff --git a/src/Plugin/HeaderAppendPlugin.php b/src/Plugin/HeaderAppendPlugin.php
index 26fd813..e6d6235 100644
--- a/src/Plugin/HeaderAppendPlugin.php
+++ b/src/Plugin/HeaderAppendPlugin.php
@@ -1,8 +1,11 @@
headers = $headers;
}
- /**
- * {@inheritdoc}
- */
- public function handleRequest(RequestInterface $request, callable $next, callable $first)
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
{
foreach ($this->headers as $header => $headerValue) {
$request = $request->withAddedHeader($header, $headerValue);
diff --git a/src/Plugin/HeaderDefaultsPlugin.php b/src/Plugin/HeaderDefaultsPlugin.php
index 6dfc111..baf8f9d 100644
--- a/src/Plugin/HeaderDefaultsPlugin.php
+++ b/src/Plugin/HeaderDefaultsPlugin.php
@@ -1,8 +1,11 @@
headers = $headers;
}
- /**
- * {@inheritdoc}
- */
- public function handleRequest(RequestInterface $request, callable $next, callable $first)
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
{
foreach ($this->headers as $header => $headerValue) {
if (!$request->hasHeader($header)) {
diff --git a/src/Plugin/HeaderRemovePlugin.php b/src/Plugin/HeaderRemovePlugin.php
index fc9c19d..abfb922 100644
--- a/src/Plugin/HeaderRemovePlugin.php
+++ b/src/Plugin/HeaderRemovePlugin.php
@@ -1,8 +1,11 @@
headers = $headers;
}
- /**
- * {@inheritdoc}
- */
- public function handleRequest(RequestInterface $request, callable $next, callable $first)
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
{
foreach ($this->headers as $header) {
if ($request->hasHeader($header)) {
diff --git a/src/Plugin/HeaderSetPlugin.php b/src/Plugin/HeaderSetPlugin.php
index 75f11d4..1ca9fb3 100644
--- a/src/Plugin/HeaderSetPlugin.php
+++ b/src/Plugin/HeaderSetPlugin.php
@@ -1,8 +1,11 @@
headers = $headers;
}
- /**
- * {@inheritdoc}
- */
- public function handleRequest(RequestInterface $request, callable $next, callable $first)
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
{
foreach ($this->headers as $header => $headerValue) {
$request = $request->withHeader($header, $headerValue);
diff --git a/src/Plugin/HistoryPlugin.php b/src/Plugin/HistoryPlugin.php
index 5abddbd..15597ee 100644
--- a/src/Plugin/HistoryPlugin.php
+++ b/src/Plugin/HistoryPlugin.php
@@ -1,9 +1,12 @@
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;
@@ -40,7 +37,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
$journal->addSuccess($request, $response);
return $response;
- }, function (Exception $exception) use ($request, $journal) {
+ }, function (ClientExceptionInterface $exception) use ($request, $journal) {
$journal->addFailure($request, $exception);
throw $exception;
diff --git a/src/Plugin/Journal.php b/src/Plugin/Journal.php
index 15f3095..9faa938 100644
--- a/src/Plugin/Journal.php
+++ b/src/Plugin/Journal.php
@@ -1,8 +1,10 @@
queryParams = $queryParams;
}
- /**
- * {@inheritdoc}
- */
- public function handleRequest(RequestInterface $request, callable $next, callable $first)
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
{
$uri = $request->getUri();
diff --git a/src/Plugin/RedirectPlugin.php b/src/Plugin/RedirectPlugin.php
index d2f442e..8aebcbf 100644
--- a/src/Plugin/RedirectPlugin.php
+++ b/src/Plugin/RedirectPlugin.php
@@ -1,15 +1,23 @@
*/
-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,33 +86,40 @@ 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
+ * @var string[][] Chain identifier => list of URLs for this chain
+ */
+ private $circularDetection = [];
+
+ /**
+ * @var StreamFactoryInterface|null
*/
- protected $circularDetection = [];
+ private $streamFactory;
/**
- * @param array $config {
+ * @param array{'preserve_header'?: bool|string[], 'use_default_for_multiple'?: bool, 'strict'?: bool, 'stream_factory'?:StreamFactoryInterface} $config
*
- * @var bool|string[] $preserve_header True keeps all headers, false remove all of them, an array is interpreted as a list of header names to keep
- * @var bool $use_default_for_multiple Whether the location header must be directly used for a multiple redirection status code (300).
- * }
+ * Configuration options:
+ * - preserve_header: True keeps all headers, false remove all of them, an array is interpreted as a list of header names to keep
+ * - use_default_for_multiple: Whether the location header must be directly used for a multiple redirection status code (300)
+ * - strict: When true, redirect codes 300, 301, 302 will not modify request method and body
+ * - stream_factory: If set, must be a PSR-17 StreamFactoryInterface - if not set, we try to discover one
*/
public function __construct(array $config = [])
{
@@ -112,9 +127,13 @@ public function __construct(array $config = [])
$resolver->setDefaults([
'preserve_header' => true,
'use_default_for_multiple' => true,
+ 'strict' => false,
+ 'stream_factory' => null,
]);
$resolver->setAllowedTypes('preserve_header', ['bool', 'array']);
$resolver->setAllowedTypes('use_default_for_multiple', 'bool');
+ $resolver->setAllowedTypes('strict', 'bool');
+ $resolver->setAllowedTypes('stream_factory', [StreamFactoryInterface::class, 'null']);
$resolver->setNormalizer('preserve_header', function (OptionsResolver $resolver, $value) {
if (is_bool($value) && false === $value) {
return [];
@@ -122,16 +141,24 @@ public function __construct(array $config = [])
return $value;
});
+ $resolver->setDefault('stream_factory', function (Options $options): ?StreamFactoryInterface {
+ return $this->guessStreamFactory();
+ });
$options = $resolver->resolve($config);
$this->preserveHeader = $options['preserve_header'];
$this->useDefaultForMultiple = $options['use_default_for_multiple'];
+
+ if ($options['strict']) {
+ $this->redirectCodes[300]['switch'] = false;
+ $this->redirectCodes[301]['switch'] = false;
+ $this->redirectCodes[302]['switch'] = false;
+ }
+
+ $this->streamFactory = $options['stream_factory'];
}
- /**
- * {@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 +169,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)) {
@@ -159,7 +186,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
$this->circularDetection[$chainIdentifier][] = (string) $request->getUri();
- if (in_array((string) $redirectRequest->getUri(), $this->circularDetection[$chainIdentifier])) {
+ if (in_array((string) $redirectRequest->getUri(), $this->circularDetection[$chainIdentifier], true)) {
throw new CircularRedirectionException('Circular redirection detected', $request, $response);
}
@@ -170,72 +197,130 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
];
}
- // Call redirect request in synchrone
- $redirectPromise = $first($redirectRequest);
-
- return $redirectPromise->wait();
+ // Call redirect request synchronously
+ return $first($redirectRequest)->wait();
});
}
/**
- * Builds the redirect request.
- *
- * @param RequestInterface $request Original request
- * @param UriInterface $uri New uri
- * @param int $statusCode Status code from the redirect response
- *
- * @return MessageInterface|RequestInterface
+ * The default only needs to be determined if no value is provided.
*/
- protected function buildRedirectRequest(RequestInterface $request, UriInterface $uri, $statusCode)
+ public function guessStreamFactory(): ?StreamFactoryInterface
{
- $request = $request->withUri($uri);
+ if (class_exists(Psr17FactoryDiscovery::class)) {
+ try {
+ return Psr17FactoryDiscovery::findStreamFactory();
+ } catch (\Throwable $t) {
+ // ignore and try other options
+ }
+ }
+ if (class_exists(Psr17Factory::class)) {
+ return new Psr17Factory();
+ }
+ if (class_exists(Utils::class)) {
+ return new class implements StreamFactoryInterface {
+ public function createStream(string $content = ''): StreamInterface
+ {
+ return Utils::streamFor($content);
+ }
+
+ public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
+ {
+ throw new \RuntimeException('Internal error: this method should not be needed');
+ }
- if (false !== $this->redirectCodes[$statusCode]['switch'] && !in_array($request->getMethod(), $this->redirectCodes[$statusCode]['switch']['unless'])) {
- $request = $request->withMethod($this->redirectCodes[$statusCode]['switch']['to']);
+ public function createStreamFromResource($resource): StreamInterface
+ {
+ throw new \RuntimeException('Internal error: this method should not be needed');
+ }
+ };
+ }
+
+ return null;
+ }
+
+ private function buildRedirectRequest(RequestInterface $originalRequest, UriInterface $targetUri, int $statusCode): RequestInterface
+ {
+ $originalRequest = $originalRequest->withUri($targetUri);
+
+ if (false !== $this->redirectCodes[$statusCode]['switch'] && !in_array($originalRequest->getMethod(), $this->redirectCodes[$statusCode]['switch']['unless'], true)) {
+ $originalRequest = $originalRequest->withMethod($this->redirectCodes[$statusCode]['switch']['to']);
+ if ('GET' === $this->redirectCodes[$statusCode]['switch']['to'] && $this->streamFactory) {
+ // if we found a stream factory, remove the request body. otherwise leave the body there.
+ $originalRequest = $originalRequest->withoutHeader('content-type');
+ $originalRequest = $originalRequest->withoutHeader('content-length');
+ $originalRequest = $originalRequest->withBody($this->streamFactory->createStream());
+ }
}
if (is_array($this->preserveHeader)) {
- $headers = array_keys($request->getHeaders());
+ $headers = array_keys($originalRequest->getHeaders());
foreach ($headers as $name) {
- if (!in_array($name, $this->preserveHeader)) {
- $request = $request->withoutHeader($name);
+ if (!in_array($name, $this->preserveHeader, true)) {
+ $originalRequest = $originalRequest->withoutHeader($name);
}
}
}
- return $request;
+ return $originalRequest;
}
/**
* Creates a new Uri from the old request and the location header.
*
- * @param ResponseInterface $response The redirect response
- * @param RequestInterface $request The original request
- *
* @throws HttpException If location header is not usable (missing or incorrect)
* @throws MultipleRedirectionException If a 300 status code is received and default location cannot be resolved (doesn't use the location header or not present)
- *
- * @return UriInterface
*/
- private function createUri(ResponseInterface $response, RequestInterface $request)
+ private function createUri(ResponseInterface $redirectResponse, RequestInterface $originalRequest): UriInterface
{
- if ($this->redirectCodes[$response->getStatusCode()]['multiple'] && (!$this->useDefaultForMultiple || !$response->hasHeader('Location'))) {
- throw new MultipleRedirectionException('Cannot choose a redirection', $request, $response);
+ if ($this->redirectCodes[$redirectResponse->getStatusCode()]['multiple'] && (!$this->useDefaultForMultiple || !$redirectResponse->hasHeader('Location'))) {
+ throw new MultipleRedirectionException('Cannot choose a redirection', $originalRequest, $redirectResponse);
}
- if (!$response->hasHeader('Location')) {
- throw new HttpException('Redirect status code, but no location header present in the response', $request, $response);
+ if (!$redirectResponse->hasHeader('Location')) {
+ throw new HttpException('Redirect status code, but no location header present in the response', $originalRequest, $redirectResponse);
}
- $location = $response->getHeaderLine('Location');
+ $location = $redirectResponse->getHeaderLine('Location');
$parsedLocation = parse_url($location);
- if (false === $parsedLocation) {
- throw new HttpException(sprintf('Location %s could not be parsed', $location), $request, $response);
+ if (false === $parsedLocation || '' === $location) {
+ throw new HttpException(sprintf('Location "%s" could not be parsed', $location), $originalRequest, $redirectResponse);
}
- $uri = $request->getUri();
+ $uri = $originalRequest->getUri();
+
+ // Redirections can either use an absolute uri or a relative reference https://www.rfc-editor.org/rfc/rfc3986#section-4.2
+ // If relative, we need to check if we have an absolute path or not
+
+ $path = array_key_exists('path', $parsedLocation) ? $parsedLocation['path'] : '';
+ if (!array_key_exists('host', $parsedLocation) && '/' !== $location[0]) {
+ // the target is a relative-path reference, we need to merge it with the base path
+ $originalPath = $uri->getPath();
+ if ('' === $path) {
+ $path = $originalPath;
+ } elseif (($pos = strrpos($originalPath, '/')) !== false) {
+ $path = substr($originalPath, 0, $pos + 1).$path;
+ } else {
+ $path = '/'.$path;
+ }
+ /* replace '/./' or '/foo/../' with '/' */
+ $re = ['#(/\./)#', '#/(?!\.\.)[^/]+/\.\./#'];
+ for ($n = 1; $n > 0; $path = preg_replace($re, '/', $path, -1, $n)) {
+ if (null === $path) {
+ throw new HttpException(sprintf('Failed to resolve Location %s', $location), $originalRequest, $redirectResponse);
+ }
+ }
+ }
+ if (null === $path) {
+ throw new HttpException(sprintf('Failed to resolve Location %s', $location), $originalRequest, $redirectResponse);
+ }
+ $uri = $uri
+ ->withPath($path)
+ ->withQuery(array_key_exists('query', $parsedLocation) ? $parsedLocation['query'] : '')
+ ->withFragment(array_key_exists('fragment', $parsedLocation) ? $parsedLocation['fragment'] : '')
+ ;
if (array_key_exists('scheme', $parsedLocation)) {
$uri = $uri->withScheme($parsedLocation['scheme']);
@@ -247,22 +332,8 @@ private function createUri(ResponseInterface $response, RequestInterface $reques
if (array_key_exists('port', $parsedLocation)) {
$uri = $uri->withPort($parsedLocation['port']);
- }
-
- if (array_key_exists('path', $parsedLocation)) {
- $uri = $uri->withPath($parsedLocation['path']);
- }
-
- if (array_key_exists('query', $parsedLocation)) {
- $uri = $uri->withQuery($parsedLocation['query']);
- } else {
- $uri = $uri->withQuery('');
- }
-
- if (array_key_exists('fragment', $parsedLocation)) {
- $uri = $uri->withFragment($parsedLocation['fragment']);
- } else {
- $uri = $uri->withFragment('');
+ } elseif (array_key_exists('host', $parsedLocation)) {
+ $uri = $uri->withPort(null);
}
return $uri;
diff --git a/src/Plugin/RequestMatcherPlugin.php b/src/Plugin/RequestMatcherPlugin.php
index 5f72b02..eb97d92 100644
--- a/src/Plugin/RequestMatcherPlugin.php
+++ b/src/Plugin/RequestMatcherPlugin.php
@@ -1,9 +1,12 @@
requestMatcher = $requestMatcher;
- $this->delegatedPlugin = $delegatedPlugin;
+ $this->successPlugin = $delegateOnMatch;
+ $this->failurePlugin = $delegateOnNoMatch;
}
- /**
- * {@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);
+ if (null !== $this->successPlugin) {
+ return $this->successPlugin->handleRequest($request, $next, $first);
+ }
+ } elseif (null !== $this->failurePlugin) {
+ return $this->failurePlugin->handleRequest($request, $next, $first);
}
return $next($request);
diff --git a/src/Plugin/RequestSeekableBodyPlugin.php b/src/Plugin/RequestSeekableBodyPlugin.php
new file mode 100644
index 0000000..f39c88d
--- /dev/null
+++ b/src/Plugin/RequestSeekableBodyPlugin.php
@@ -0,0 +1,26 @@
+
+ */
+final class RequestSeekableBodyPlugin extends SeekableBodyPlugin
+{
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
+ {
+ if (!$request->getBody()->isSeekable()) {
+ $request = $request->withBody(new BufferedStream($request->getBody(), $this->useFileBuffer, $this->memoryBufferSize));
+ }
+
+ return $next($request);
+ }
+}
diff --git a/src/Plugin/ResponseSeekableBodyPlugin.php b/src/Plugin/ResponseSeekableBodyPlugin.php
new file mode 100644
index 0000000..ef8bee4
--- /dev/null
+++ b/src/Plugin/ResponseSeekableBodyPlugin.php
@@ -0,0 +1,29 @@
+
+ */
+final class ResponseSeekableBodyPlugin extends SeekableBodyPlugin
+{
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
+ {
+ return $next($request)->then(function (ResponseInterface $response) {
+ if ($response->getBody()->isSeekable()) {
+ return $response;
+ }
+
+ return $response->withBody(new BufferedStream($response->getBody(), $this->useFileBuffer, $this->memoryBufferSize));
+ });
+ }
+}
diff --git a/src/Plugin/RetryPlugin.php b/src/Plugin/RetryPlugin.php
index 3d09265..992bb21 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;
+ 'error_response_decider' => function (RequestInterface $request, ResponseInterface $response) {
+ // do not retry client errors
+ return $response->getStatusCode() >= 500 && $response->getStatusCode() < 600;
},
- 'exception_delay' => __CLASS__.'::defaultDelay',
+ 'exception_decider' => function (RequestInterface $request, ClientExceptionInterface $e) {
+ // do not retry client errors
+ return !$e instanceof HttpException || $e->getCode() >= 500 && $e->getCode() < 600;
+ },
+ 'error_response_delay' => __CLASS__.'::defaultErrorResponseDelay',
+ 'exception_delay' => __CLASS__.'::defaultExceptionDelay',
]);
+
$resolver->setAllowedTypes('retries', 'int');
+ $resolver->setAllowedTypes('error_response_decider', 'callable');
$resolver->setAllowedTypes('exception_decider', 'callable');
+ $resolver->setAllowedTypes('error_response_delay', 'callable');
$resolver->setAllowedTypes('exception_delay', 'callable');
$options = $resolver->resolve($config);
$this->retry = $options['retries'];
+ $this->errorResponseDecider = $options['error_response_decider'];
+ $this->errorResponseDelay = $options['error_response_delay'];
$this->exceptionDecider = $options['exception_decider'];
$this->exceptionDelay = $options['exception_delay'];
}
- /**
- * {@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);
- return $next($request)->then(function (ResponseInterface $response) use ($request, $chainIdentifier) {
+ return $next($request)->then(function (ResponseInterface $response) use ($request, $next, $first, $chainIdentifier) {
+ if (!array_key_exists($chainIdentifier, $this->retryStorage)) {
+ $this->retryStorage[$chainIdentifier] = 0;
+ }
+
+ if ($this->retryStorage[$chainIdentifier] >= $this->retry) {
+ unset($this->retryStorage[$chainIdentifier]);
+
+ return $response;
+ }
+
+ if (call_user_func($this->errorResponseDecider, $request, $response)) {
+ /** @var int $time */
+ $time = call_user_func($this->errorResponseDelay, $request, $response, $this->retryStorage[$chainIdentifier]);
+ $response = $this->retry($request, $next, $first, $chainIdentifier, $time);
+ }
+
if (array_key_exists($chainIdentifier, $this->retryStorage)) {
unset($this->retryStorage[$chainIdentifier]);
}
return $response;
- }, function (Exception $exception) use ($request, $next, $first, $chainIdentifier) {
+ }, function (ClientExceptionInterface $exception) use ($request, $next, $first, $chainIdentifier) {
if (!array_key_exists($chainIdentifier, $this->retryStorage)) {
$this->retryStorage[$chainIdentifier] = 0;
}
@@ -114,26 +137,40 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
throw $exception;
}
+ /** @var int $time */
$time = call_user_func($this->exceptionDelay, $request, $exception, $this->retryStorage[$chainIdentifier]);
- usleep($time);
-
- // Retry in synchrone
- ++$this->retryStorage[$chainIdentifier];
- $promise = $this->handleRequest($request, $next, $first);
- return $promise->wait();
+ return $this->retry($request, $next, $first, $chainIdentifier, $time);
});
}
/**
- * @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.
- *
- * @return int
+ * @param int $retries The number of retries we made before. First time this get called it will be 0.
+ */
+ public static function defaultErrorResponseDelay(RequestInterface $request, ResponseInterface $response, int $retries): int
+ {
+ return pow(2, $retries) * 500000;
+ }
+
+ /**
+ * @param int $retries The number of retries we made before. First time this get called it will be 0.
*/
- public static function defaultDelay(RequestInterface $request, Exception $e, $retries)
+ public static function defaultExceptionDelay(RequestInterface $request, ClientExceptionInterface $e, int $retries): int
{
return pow(2, $retries) * 500000;
}
+
+ /**
+ * @throws \Exception if retrying returns a failed promise
+ */
+ private function retry(RequestInterface $request, callable $next, callable $first, string $chainIdentifier, int $delay): ResponseInterface
+ {
+ usleep($delay);
+
+ // Retry synchronously
+ ++$this->retryStorage[$chainIdentifier];
+ $promise = $this->handleRequest($request, $next, $first);
+
+ return $promise->wait();
+ }
}
diff --git a/src/Plugin/SeekableBodyPlugin.php b/src/Plugin/SeekableBodyPlugin.php
new file mode 100644
index 0000000..1be2cde
--- /dev/null
+++ b/src/Plugin/SeekableBodyPlugin.php
@@ -0,0 +1,47 @@
+setDefaults([
+ 'use_file_buffer' => true,
+ 'memory_buffer_size' => 2097152,
+ ]);
+ $resolver->setAllowedTypes('use_file_buffer', 'bool');
+ $resolver->setAllowedTypes('memory_buffer_size', 'int');
+
+ $options = $resolver->resolve($config);
+
+ $this->useFileBuffer = $options['use_file_buffer'];
+ $this->memoryBufferSize = $options['memory_buffer_size'];
+ }
+}
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/PluginChain.php b/src/PluginChain.php
new file mode 100644
index 0000000..2bfbfbc
--- /dev/null
+++ b/src/PluginChain.php
@@ -0,0 +1,61 @@
+plugins = $plugins;
+ $this->clientCallable = $clientCallable;
+ $this->maxRestarts = (int) ($options['max_restarts'] ?? 0);
+ }
+
+ private function createChain(): callable
+ {
+ $lastCallable = $this->clientCallable;
+ $reversedPlugins = \array_reverse($this->plugins);
+
+ foreach ($reversedPlugins as $plugin) {
+ $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable) {
+ return $plugin->handleRequest($request, $lastCallable, $this);
+ };
+ }
+
+ return $lastCallable;
+ }
+
+ public function __invoke(RequestInterface $request): Promise
+ {
+ if ($this->restarts > $this->maxRestarts) {
+ throw new LoopException('Too many restarts in plugin client', $request);
+ }
+
+ ++$this->restarts;
+
+ return $this->createChain()($request);
+ }
+}
diff --git a/src/PluginClient.php b/src/PluginClient.php
index 625033b..d728c78 100644
--- a/src/PluginClient.php
+++ b/src/PluginClient.php
@@ -1,15 +1,18 @@
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 \TypeError(
+ sprintf('%s::__construct(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client))
+ );
}
$this->plugins = $plugins;
$this->options = $this->configure($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)) {
+ // If the client doesn't support sync calls, call async
+ if (!$this->client instanceof ClientInterface) {
return $this->sendAsyncRequest($request)->wait();
}
- // Else we want to use the synchronous call of the underlying client, and not the async one in the case
- // we have both an async and sync call
+ // Else we want to use the synchronous call of the underlying client,
+ // and not the async one in the case we have both an async and sync call
$pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
try {
return new HttpFulfilledPromise($this->client->sendRequest($request));
@@ -88,9 +84,6 @@ public function sendRequest(RequestInterface $request)
return $pluginChain($request)->wait();
}
- /**
- * {@inheritdoc}
- */
public function sendAsyncRequest(RequestInterface $request)
{
$pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
@@ -102,35 +95,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);
}
@@ -138,43 +111,13 @@ private function configure(array $options = [])
/**
* Create the plugin chain.
*
- * @param Plugin[] $pluginList A list of plugins
+ * @param Plugin[] $plugins A plugin chain
* @param callable $clientCallable Callable making the HTTP call
*
- * @return callable
+ * @return callable(RequestInterface): Promise
*/
- private function createPluginChain($pluginList, callable $clientCallable)
+ private function createPluginChain(array $plugins, 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)) {
- $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) {
- return $plugin->handleRequest($request, $lastCallable, $firstCallable);
- };
-
- $firstCallable = $lastCallable;
- }
-
- $firstCalls = 0;
- $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) {
- if ($firstCalls > $this->options['max_restarts']) {
- throw new LoopException('Too many restarts in plugin client', $request);
- }
-
- ++$firstCalls;
-
- return $lastCallable($request);
- };
-
- return $firstCallable;
+ return new PluginChain($plugins, $clientCallable, $this->options);
}
}
diff --git a/src/PluginClientBuilder.php b/src/PluginClientBuilder.php
new file mode 100644
index 0000000..45fd787
--- /dev/null
+++ b/src/PluginClientBuilder.php
@@ -0,0 +1,76 @@
+
+ */
+final class PluginClientBuilder
+{
+ /** @var Plugin[][] List of plugins ordered by priority [priority => Plugin[]]). */
+ private $plugins = [];
+
+ /** @var array Array of options to give to the plugin client */
+ private $options = [];
+
+ /**
+ * @param int $priority Priority of the plugin. The higher comes first.
+ */
+ public function addPlugin(Plugin $plugin, int $priority = 0): self
+ {
+ $this->plugins[$priority][] = $plugin;
+
+ return $this;
+ }
+
+ /**
+ * @param string|int|float|bool|string[] $value
+ */
+ public function setOption(string $name, $value): self
+ {
+ $this->options[$name] = $value;
+
+ return $this;
+ }
+
+ public function removeOption(string $name): self
+ {
+ unset($this->options[$name]);
+
+ return $this;
+ }
+
+ /**
+ * @param ClientInterface|HttpAsyncClient $client
+ */
+ public function createClient($client): PluginClient
+ {
+ if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) {
+ throw new \TypeError(
+ sprintf('%s::createClient(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client))
+ );
+ }
+
+ $plugins = $this->plugins;
+
+ if (0 === count($plugins)) {
+ $plugins[] = [];
+ }
+
+ krsort($plugins);
+ $plugins = array_merge(...$plugins);
+
+ return new PluginClient(
+ $client,
+ array_values($plugins),
+ $this->options
+ );
+ }
+}
diff --git a/src/PluginClientFactory.php b/src/PluginClientFactory.php
index cbea3e1..1d2b2be 100644
--- a/src/PluginClientFactory.php
+++ b/src/PluginClientFactory.php
@@ -1,9 +1,10 @@
doSendRequest($request);
}
diff --git a/tests/HttpMethodsClientTest.php b/tests/HttpMethodsClientTest.php
new file mode 100644
index 0000000..d93a2a8
--- /dev/null
+++ b/tests/HttpMethodsClientTest.php
@@ -0,0 +1,100 @@
+httpClient = $this->createMock(ClientInterface::class);
+ $streamFactory = $requestFactory = new Psr17Factory();
+ $this->httpMethodsClient = new HttpMethodsClient($this->httpClient, $requestFactory, $streamFactory);
+ }
+
+ public function testGet(): void
+ {
+ $this->expectSendRequest('get');
+ }
+
+ public function testHead(): void
+ {
+ $this->expectSendRequest('head');
+ }
+
+ public function testTrace(): void
+ {
+ $this->expectSendRequest('trace');
+ }
+
+ public function testPost(): void
+ {
+ $this->expectSendRequest('post', self::BODY);
+ }
+
+ public function testPut(): void
+ {
+ $this->expectSendRequest('put', self::BODY);
+ }
+
+ public function testPatch(): void
+ {
+ $this->expectSendRequest('patch', self::BODY);
+ }
+
+ public function testDelete(): void
+ {
+ $this->expectSendRequest('delete', self::BODY);
+ }
+
+ public function testOptions(): void
+ {
+ $this->expectSendRequest('options', self::BODY);
+ }
+
+ /**
+ * Run the actual test.
+ *
+ * As there is no data provider in phpspec, we keep separate methods to get new mocks for each test.
+ */
+ private function expectSendRequest(string $method, ?string $body = null): void
+ {
+ $response = new Response();
+ $this->httpClient->expects($this->once())
+ ->method('sendRequest')
+ ->with(self::callback(static function (RequestInterface $request) use ($body, $method): bool {
+ self::assertSame(strtoupper($method), $request->getMethod());
+ self::assertSame(self::URI, (string) $request->getUri());
+ self::assertSame([self::HEADER_NAME => [self::HEADER_VALUE]], $request->getHeaders());
+ self::assertSame((string) $body, (string) $request->getBody());
+
+ return true;
+ }))
+ ->willReturn($response)
+ ;
+
+ $actualResponse = $this->httpMethodsClient->$method(self::URI, [self::HEADER_NAME => self::HEADER_VALUE], self::BODY);
+ $this->assertSame($response, $actualResponse);
+ }
+}
diff --git a/tests/Plugin/AddPathPluginTest.php b/tests/Plugin/AddPathPluginTest.php
new file mode 100644
index 0000000..9457535
--- /dev/null
+++ b/tests/Plugin/AddPathPluginTest.php
@@ -0,0 +1,96 @@
+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());
+
+ return new HttpFulfilledPromise(new Response());
+ };
+
+ $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());
+
+ return new HttpFulfilledPromise(new Response());
+ }, $this->first);
+
+ return new HttpFulfilledPromise(new Response());
+ }, $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());
+
+ return new HttpFulfilledPromise(new Response());
+ }, $this->first);
+
+ $request = new Request('GET', 'https://example.com/bar');
+ $this->plugin->handleRequest($request, function (RequestInterface $request) {
+ $this->assertEquals('https://example.com/api/bar', $request->getUri()->__toString());
+
+ return new HttpFulfilledPromise(new Response());
+ }, $this->first);
+ }
+
+ public function testRewriteWhenPathIsIncluded()
+ {
+ $verify = function (RequestInterface $request) {
+ $this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString());
+
+ return new HttpFulfilledPromise(new Response());
+ };
+
+ $request = new Request('GET', 'https://example.com/api/foo');
+ $this->plugin->handleRequest($request, $verify, $this->first);
+ }
+}
diff --git a/tests/Plugin/RedirectPluginTest.php b/tests/Plugin/RedirectPluginTest.php
new file mode 100644
index 0000000..92072ad
--- /dev/null
+++ b/tests/Plugin/RedirectPluginTest.php
@@ -0,0 +1,116 @@
+expectException(CircularRedirectionException::class);
+ (new RedirectPlugin())->handleRequest(
+ new Request('GET', 'https://example.com/path?query=value'),
+ function () {
+ return new FulfilledPromise(new Response(302, ['Location' => 'https://example.com/path?query=value']));
+ },
+ function () {}
+ )->wait();
+ }
+
+ public function testPostGetDropRequestBody(): void
+ {
+ $response = (new RedirectPlugin())->handleRequest(
+ new Request('POST', 'https://example.com/path', ['Content-Type' => 'text/plain', 'Content-Length' => '10'], (new Psr17Factory())->createStream('hello test')),
+ function (RequestInterface $request) {
+ $this->assertSame(10, $request->getBody()->getSize());
+ $this->assertTrue($request->hasHeader('Content-Type'));
+ $this->assertTrue($request->hasHeader('Content-Length'));
+
+ return new FulfilledPromise(new Response(302, ['Location' => 'https://example.com/other']));
+ },
+ function (RequestInterface $request) {
+ $this->assertSame('GET', $request->getMethod());
+ $this->assertSame(0, $request->getBody()->getSize());
+ $this->assertFalse($request->hasHeader('Content-Type'));
+ $this->assertFalse($request->hasHeader('Content-Length'));
+
+ return new FulfilledPromise(new Response(200, ['uri' => $request->getUri()->__toString()]));
+ }
+ )->wait();
+
+ $this->assertSame('https://example.com/other', $response->getHeaderLine('uri'));
+ }
+
+ public function testPostGetNoFactory(): void
+ {
+ // We explicitly set the stream factory to null. Same happens if no factory can be found.
+ // In this case, the redirect will leave the body alone.
+ $response = (new RedirectPlugin(['stream_factory' => null]))->handleRequest(
+ new Request('POST', 'https://example.com/path', ['Content-Type' => 'text/plain', 'Content-Length' => '10'], (new Psr17Factory())->createStream('hello test')),
+ function (RequestInterface $request) {
+ $this->assertSame(10, $request->getBody()->getSize());
+ $this->assertTrue($request->hasHeader('Content-Type'));
+ $this->assertTrue($request->hasHeader('Content-Length'));
+
+ return new FulfilledPromise(new Response(302, ['Location' => 'https://example.com/other']));
+ },
+ function (RequestInterface $request) {
+ $this->assertSame('GET', $request->getMethod());
+ $this->assertSame(10, $request->getBody()->getSize());
+ $this->assertTrue($request->hasHeader('Content-Type'));
+ $this->assertTrue($request->hasHeader('Content-Length'));
+
+ return new FulfilledPromise(new Response(200, ['uri' => $request->getUri()->__toString()]));
+ }
+ )->wait();
+
+ $this->assertSame('https://example.com/other', $response->getHeaderLine('uri'));
+ }
+
+ public function provideRedirections(): array
+ {
+ return [
+ 'no path on target' => ['https://example.com/path?query=value', 'https://example.com?query=value', 'https://example.com?query=value'],
+ 'root path on target' => ['https://example.com/path?query=value', 'https://example.com/?query=value', 'https://example.com/?query=value'],
+ 'redirect to query' => ['https://example.com', 'https://example.com?query=value', 'https://example.com?query=value'],
+ 'redirect to different domain without port' => ['https://example.com:8000', 'https://foo.com?query=value', 'https://foo.com?query=value'],
+ 'network-path redirect, preserve scheme' => ['https://example.com:8000', '//foo.com/path?query=value', 'https://foo.com/path?query=value'],
+ 'absolute-path redirect, preserve host' => ['https://example.com:8000', '/path?query=value', 'https://example.com:8000/path?query=value'],
+ 'relative-path redirect, append' => ['https://example.com:8000/path/', 'sub/path?query=value', 'https://example.com:8000/path/sub/path?query=value'],
+ 'relative-path on non-folder' => ['https://example.com:8000/path/foo', 'sub/path?query=value', 'https://example.com:8000/path/sub/path?query=value'],
+ 'relative-path moving up' => ['https://example.com:8000/path/', '../other?query=value', 'https://example.com:8000/other?query=value'],
+ 'relative-path with ./' => ['https://example.com:8000/path/', './other?query=value', 'https://example.com:8000/path/other?query=value'],
+ 'relative-path with //' => ['https://example.com:8000/path/', 'other//sub?query=value', 'https://example.com:8000/path/other//sub?query=value'],
+ 'relative-path redirect with only query' => ['https://example.com:8000/path', '?query=value', 'https://example.com:8000/path?query=value'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideRedirections
+ */
+ public function testTargetUriMappingFromLocationHeader(string $originalUri, string $locationUri, string $targetUri): void
+ {
+ $response = (new RedirectPlugin())->handleRequest(
+ new Request('GET', $originalUri),
+ function () use ($locationUri) {
+ return new FulfilledPromise(new Response(302, ['Location' => $locationUri]));
+ },
+ function (RequestInterface $request) {
+ return new FulfilledPromise(new Response(200, ['uri' => $request->getUri()->__toString()]));
+ }
+ )->wait();
+ $this->assertInstanceOf(ResponseInterface::class, $response);
+ $this->assertSame($targetUri, $response->getHeaderLine('uri'));
+ }
+}
diff --git a/tests/PluginChainTest.php b/tests/PluginChainTest.php
new file mode 100644
index 0000000..7a53681
--- /dev/null
+++ b/tests/PluginChainTest.php
@@ -0,0 +1,91 @@
+func = $func;
+ }
+
+ public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
+ {
+ ($this->func)($request, $next, $first);
+
+ return $next($request);
+ }
+ };
+ }
+
+ public function testChainShouldInvokePluginsInReversedOrder(): void
+ {
+ $pluginOrderCalls = [];
+
+ $plugin1 = $this->createPlugin(static function () use (&$pluginOrderCalls) {
+ $pluginOrderCalls[] = 'plugin1';
+ });
+ $plugin2 = $this->createPlugin(static function () use (&$pluginOrderCalls) {
+ $pluginOrderCalls[] = 'plugin2';
+ });
+
+ $request = $this->prophesize(RequestInterface::class);
+ $responsePromise = $this->prophesize(Promise::class);
+
+ $clientCallable = static function () use ($responsePromise) {
+ return $responsePromise->reveal();
+ };
+
+ $pluginOrderCalls = [];
+
+ $plugins = [
+ $plugin1,
+ $plugin2,
+ ];
+
+ $pluginChain = new PluginChain($plugins, $clientCallable);
+
+ $result = $pluginChain($request->reveal());
+
+ $this->assertSame($responsePromise->reveal(), $result);
+ $this->assertSame(['plugin1', 'plugin2'], $pluginOrderCalls);
+ }
+
+ public function testShouldThrowLoopExceptionOnMaxRestarts(): void
+ {
+ $this->expectException(LoopException::class);
+
+ $request = $this->prophesize(RequestInterface::class);
+ $responsePromise = $this->prophesize(Promise::class);
+ $calls = 0;
+ $clientCallable = static function () use ($responsePromise, &$calls) {
+ ++$calls;
+
+ return $responsePromise->reveal();
+ };
+
+ $pluginChain = new PluginChain([], $clientCallable, ['max_restarts' => 2]);
+
+ $pluginChain($request->reveal());
+ $this->assertSame(1, $calls);
+ $pluginChain($request->reveal());
+ $this->assertSame(2, $calls);
+ $pluginChain($request->reveal());
+ $this->assertSame(3, $calls);
+ $pluginChain($request->reveal());
+ }
+}
diff --git a/tests/PluginClientBuilderTest.php b/tests/PluginClientBuilderTest.php
new file mode 100644
index 0000000..9372859
--- /dev/null
+++ b/tests/PluginClientBuilderTest.php
@@ -0,0 +1,79 @@
+ $this->prophesize(Plugin::class)->reveal(),
+ -10 => $this->prophesize(Plugin::class)->reveal(),
+ 0 => $this->prophesize(Plugin::class)->reveal(),
+ ];
+
+ foreach ($plugins as $priority => $plugin) {
+ $builder->addPlugin($plugin, $priority);
+ }
+
+ $client = $this->prophesize($client)->reveal();
+ $client = $builder->createClient($client);
+
+ $closure = \Closure::bind(
+ function (): array {
+ return $this->plugins;
+ },
+ $client,
+ PluginClient::class
+ );
+
+ $plugged = $closure();
+
+ $expected = $plugins;
+ krsort($expected);
+ $expected = array_values($expected);
+
+ $this->assertSame($expected, $plugged);
+ }
+
+ /** @dataProvider clientProvider */
+ public function testOptions(string $client): void
+ {
+ $builder = new PluginClientBuilder();
+ $builder->setOption('max_restarts', 5);
+
+ $client = $this->prophesize($client)->reveal();
+ $client = $builder->createClient($client);
+
+ $closure = \Closure::bind(
+ function (): array {
+ return $this->options;
+ },
+ $client,
+ PluginClient::class
+ );
+
+ $options = $closure();
+
+ $this->assertArrayHasKey('max_restarts', $options);
+ $this->assertSame(5, $options['max_restarts']);
+ }
+
+ public function clientProvider(): iterable
+ {
+ yield 'sync\'d http client' => [HttpClient::class];
+ yield 'async\'d http client' => [HttpAsyncClient::class];
+ }
+}
diff --git a/tests/PluginClientTest.php b/tests/PluginClientTest.php
new file mode 100644
index 0000000..26547bd
--- /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];
+ }
+}