From 49ca33c219bd949bfa21b8a2cf3c7a358c7af89c Mon Sep 17 00:00:00 2001 From: Markus Reinhold Date: Sun, 13 Mar 2022 11:52:30 +0100 Subject: [PATCH 1/8] Provide PSR-18 adapter #11 --- composer.json | 9 +- composer.lock | 669 +++++++++++++++++- src/HttpAdapter/Psr18ClientAdapter.php | 72 ++ src/HttpAdapter/Psr7ResponseAdapter.php | 28 + .../HttpAdapter/ClientTestCase.php | 110 +++ .../HttpStreamWrapperClientTest.php | 112 +-- .../HttpAdapter/Psr18ClientTest.php | 25 + 7 files changed, 916 insertions(+), 109 deletions(-) create mode 100644 src/HttpAdapter/Psr18ClientAdapter.php create mode 100644 src/HttpAdapter/Psr7ResponseAdapter.php create mode 100644 tests/Integration/HttpAdapter/ClientTestCase.php create mode 100644 tests/Integration/HttpAdapter/Psr18ClientTest.php diff --git a/composer.json b/composer.json index fe0fd39..7553b2a 100644 --- a/composer.json +++ b/composer.json @@ -20,12 +20,17 @@ }, "require": { "php": "^7.4 || ^8.0", - "ext-json": "*" + "ext-json": "*", + "psr/http-client": "^1.0", + "psr/http-message": "^1.0", + "psr/http-factory": "^1.0" }, "require-dev": { "phpunit/phpunit": "^9.5", "symfony/process": "^5.4", "squizlabs/php_codesniffer": "^3.6", - "phpstan/phpstan": "^1.2" + "phpstan/phpstan": "^1.2", + "symfony/http-client": "^5.4", + "nyholm/psr7": "^1.5" } } diff --git a/composer.lock b/composer.lock index 222a3f3..c025ae7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,160 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "90eecf44ea84544e4c2a84f219bad999", - "packages": [], + "content-hash": "3ae070ca2b604a15c991eeaf4308013a", + "packages": [ + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + } + ], "packages-dev": [ { "name": "doctrine/instantiator", @@ -158,6 +310,69 @@ ], "time": "2021-11-30T19:35:32+00:00" }, + { + "name": "nyholm/psr7", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "1461e07a0f2a975a52082ca3b769ca912b816226" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/1461e07a0f2a975a52082ca3b769ca912b816226", + "reference": "1461e07a0f2a975a52082ca3b769ca912b816226", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "php-http/message-factory": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || 8.5 || 9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "time": "2022-02-02T18:37:57+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.3", @@ -261,6 +476,56 @@ "description": "Library for handling version information and constraints", "time": "2021-02-23T14:00:09+00:00" }, + { + "name": "php-http/message-factory", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "time": "2015-12-19T14:08:53+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -871,6 +1136,97 @@ ], "time": "2021-12-25T07:07:57+00:00" }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2021-05-03T11:20:27+00:00" + }, { "name": "sebastian/cli-parser", "version": "1.0.1", @@ -1726,6 +2082,187 @@ ], "time": "2021-12-12T21:44:58+00:00" }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "time": "2021-07-12T14:48:14+00:00" + }, + { + "name": "symfony/http-client", + "version": "v5.4.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "fab84798694e45b4571d305125215699eb2b1f73" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/fab84798694e45b4571d305125215699eb2b1f73", + "reference": "fab84798694e45b4571d305125215699eb2b1f73", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-client-contracts": "^2.4", + "symfony/polyfill-php73": "^1.11", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "2.4" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "time": "2022-02-27T08:46:18+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "ec82e57b5b714dbb69300d348bd840b345e24166" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ec82e57b5b714dbb69300d348bd840b345e24166", + "reference": "ec82e57b5b714dbb69300d348bd840b345e24166", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2021-11-03T09:24:47+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.24.0", @@ -1791,6 +2328,68 @@ ], "time": "2021-10-20T20:35:02+00:00" }, + { + "name": "symfony/polyfill-php73", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5", + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2021-06-05T21:20:04+00:00" + }, { "name": "symfony/polyfill-php80", "version": "v1.24.0", @@ -1902,6 +2501,72 @@ "homepage": "https://symfony.com", "time": "2021-12-27T21:01:00+00:00" }, + { + "name": "symfony/service-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2021-11-04T16:48:04+00:00" + }, { "name": "theseer/tokenizer", "version": "1.2.1", diff --git a/src/HttpAdapter/Psr18ClientAdapter.php b/src/HttpAdapter/Psr18ClientAdapter.php new file mode 100644 index 0000000..36b6bd7 --- /dev/null +++ b/src/HttpAdapter/Psr18ClientAdapter.php @@ -0,0 +1,72 @@ +client = $client; + $this->requestFactory = $requestFactory; + $this->streamFactory = $streamFactory; + } + + public function get(Request $request): Response + { + return $this->request('GET', $request); + } + + public function post(Request $request): Response + { + return $this->request('POST', $request); + } + + public function delete(Request $request): Response + { + return $this->request('DELETE', $request); + } + + /** + * @throws NchanException + */ + private function request(string $method, Request $request): Response + { + try { + $psrRequest = $this->requestFactory + ->createRequest($method, $request->url()->toString()) + ->withBody($this->streamFactory->createStream($request->body())); + + foreach ($request->headers() as $name => $value) { + $psrRequest = $psrRequest->withHeader($name, $value); + } + + return new Psr7ResponseAdapter($this->client->sendRequest($psrRequest)); + } catch (ClientExceptionInterface $exception) { + throw new NchanException( + $exception->getMessage(), + $exception->getCode(), + $exception + ); + } + } +} diff --git a/src/HttpAdapter/Psr7ResponseAdapter.php b/src/HttpAdapter/Psr7ResponseAdapter.php new file mode 100644 index 0000000..be6486c --- /dev/null +++ b/src/HttpAdapter/Psr7ResponseAdapter.php @@ -0,0 +1,28 @@ +response = $response; + } + + public function statusCode(): int + { + return $this->response->getStatusCode(); + } + + public function body(): string + { + return (string)$this->response->getBody(); + } +} diff --git a/tests/Integration/HttpAdapter/ClientTestCase.php b/tests/Integration/HttpAdapter/ClientTestCase.php new file mode 100644 index 0000000..bea35f9 --- /dev/null +++ b/tests/Integration/HttpAdapter/ClientTestCase.php @@ -0,0 +1,110 @@ + 'application/json' + ] + ); + + $client = $this->createClient($request); + + $response = $client->get($request); + + $serverResponse = unserialize($response->body()); + + $this->assertSame(201, $response->statusCode()); + $this->assertSame('application/json', $serverResponse['SERVER']['HTTP_ACCEPT']); + } + + /** + * @test + */ + public function itShouldPerformPostRequest(): void + { + $request = new Request( + new Url(getenv('INTEGRATION_TEST_BASE_URL') . '?statusCode=201'), + [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/x-www-form-urlencoded' + ], + http_build_query( + [ + 'message' => 'my-message-name' + ] + ) + ); + + $client = $this->createClient($request); + + $response = $client->post($request); + + $serverResponse = unserialize($response->body()); + + $this->assertSame(201, $response->statusCode()); + $this->assertSame('application/json', $serverResponse['SERVER']['HTTP_ACCEPT']); + $this->assertSame('my-message-name', $serverResponse['POST']['message']); + } + + /** + * @test + */ + public function itShouldPerformDeleteRequest(): void + { + $request = new Request( + new Url(getenv('INTEGRATION_TEST_BASE_URL') . '?statusCode=201'), + [ + 'Accept' => 'application/json' + ] + ); + + $client = $this->createClient($request); + + $response = $client->delete($request); + + $serverResponse = unserialize($response->body()); + + $this->assertSame(201, $response->statusCode()); + $this->assertSame('application/json', $serverResponse['SERVER']['HTTP_ACCEPT']); + } + + /** + * @test + */ + public function itShouldThrowExceptionOnInvalidResponse(): void + { + $this->expectException(NchanException::class); + + $request = new Request( + new Url(getenv('INTEGRATION_TEST_INVALID_BASE_URL')), + [ + 'Accept' => 'application/json' + ] + ); + + $client = $this->createClient($request); + + $client->get($request); + } +} diff --git a/tests/Integration/HttpAdapter/HttpStreamWrapperClientTest.php b/tests/Integration/HttpAdapter/HttpStreamWrapperClientTest.php index f3a9597..48fc63f 100644 --- a/tests/Integration/HttpAdapter/HttpStreamWrapperClientTest.php +++ b/tests/Integration/HttpAdapter/HttpStreamWrapperClientTest.php @@ -4,126 +4,28 @@ namespace Marein\Nchan\Tests\Integration\HttpAdapter; -use Marein\Nchan\Exception\NchanException; +use Marein\Nchan\Http\Client; use Marein\Nchan\Http\Request; -use Marein\Nchan\Http\Url; use Marein\Nchan\HttpAdapter\Credentials; use Marein\Nchan\HttpAdapter\HttpStreamWrapperClient; -use PHPUnit\Framework\TestCase; -class HttpStreamWrapperClientTest extends TestCase +class HttpStreamWrapperClientTest extends ClientTestCase { - /** - * @test - */ - public function itShouldBeCreatedWithDefaults(): void - { - HttpStreamWrapperClient::withDefaults(); - - $this->assertTrue(true); - } - - /** - * @test - */ - public function itShouldPerformGetRequest(): void + protected function createClient(Request $request): Client { - $request = new Request( - new Url(getenv('INTEGRATION_TEST_BASE_URL') . '?statusCode=201'), - [ - 'Accept' => 'application/json' - ] - ); - $credentials = $this->createMock(Credentials::class); $credentials->expects($this->once())->method('authenticate')->willReturn($request); - $client = new HttpStreamWrapperClient($credentials); - - $response = $client->get($request); - - $serverResponse = unserialize($response->body()); - - $this->assertSame(201, $response->statusCode()); - $this->assertSame('application/json', $serverResponse['SERVER']['HTTP_ACCEPT']); + return new HttpStreamWrapperClient($credentials); } /** * @test */ - public function itShouldPerformPostRequest(): void - { - $request = new Request( - new Url(getenv('INTEGRATION_TEST_BASE_URL') . '?statusCode=201'), - [ - 'Accept' => 'application/json', - 'Content-Type' => 'application/x-www-form-urlencoded' - ], - http_build_query( - [ - 'message' => 'my-message-name' - ] - ) - ); - - $credentials = $this->createMock(Credentials::class); - $credentials->expects($this->once())->method('authenticate')->willReturn($request); - - $client = new HttpStreamWrapperClient($credentials); - - $response = $client->post($request); - - $serverResponse = unserialize($response->body()); - - $this->assertSame(201, $response->statusCode()); - $this->assertSame('application/json', $serverResponse['SERVER']['HTTP_ACCEPT']); - $this->assertSame('my-message-name', $serverResponse['POST']['message']); - } - - /** - * @test - */ - public function itShouldPerformDeleteRequest(): void - { - $request = new Request( - new Url(getenv('INTEGRATION_TEST_BASE_URL') . '?statusCode=201'), - [ - 'Accept' => 'application/json' - ] - ); - - $credentials = $this->createMock(Credentials::class); - $credentials->expects($this->once())->method('authenticate')->willReturn($request); - - $client = new HttpStreamWrapperClient($credentials); - - $response = $client->delete($request); - - $serverResponse = unserialize($response->body()); - - $this->assertSame(201, $response->statusCode()); - $this->assertSame('application/json', $serverResponse['SERVER']['HTTP_ACCEPT']); - } - - /** - * @test - */ - public function itShouldThrowExceptionOnInvalidResponse(): void + public function itShouldBeCreatedWithDefaults(): void { - $this->expectException(NchanException::class); - - $request = new Request( - new Url(getenv('INTEGRATION_TEST_INVALID_BASE_URL')), - [ - 'Accept' => 'application/json' - ] - ); - - $credentials = $this->createMock(Credentials::class); - $credentials->expects($this->once())->method('authenticate')->willReturn($request); - - $client = new HttpStreamWrapperClient($credentials); + HttpStreamWrapperClient::withDefaults(); - $client->get($request); + $this->assertTrue(true); } } diff --git a/tests/Integration/HttpAdapter/Psr18ClientTest.php b/tests/Integration/HttpAdapter/Psr18ClientTest.php new file mode 100644 index 0000000..9583eba --- /dev/null +++ b/tests/Integration/HttpAdapter/Psr18ClientTest.php @@ -0,0 +1,25 @@ + Date: Sun, 13 Mar 2022 11:57:30 +0100 Subject: [PATCH 2/8] Rename test classes #11 --- .../{Psr18ClientTest.php => Psr18ClientAdapterTest.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/Integration/HttpAdapter/{Psr18ClientTest.php => Psr18ClientAdapterTest.php} (91%) diff --git a/tests/Integration/HttpAdapter/Psr18ClientTest.php b/tests/Integration/HttpAdapter/Psr18ClientAdapterTest.php similarity index 91% rename from tests/Integration/HttpAdapter/Psr18ClientTest.php rename to tests/Integration/HttpAdapter/Psr18ClientAdapterTest.php index 9583eba..d42f244 100644 --- a/tests/Integration/HttpAdapter/Psr18ClientTest.php +++ b/tests/Integration/HttpAdapter/Psr18ClientAdapterTest.php @@ -10,7 +10,7 @@ use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\Psr18Client; -class Psr18ClientTest extends ClientTestCase +class Psr18ClientAdapterTest extends ClientTestCase { protected function createClient(Request $request): Client { From 17fd39da5972e14afe79269620d263d2831041c4 Mon Sep 17 00:00:00 2001 From: Markus Reinhold Date: Sun, 13 Mar 2022 12:00:48 +0100 Subject: [PATCH 3/8] Pass http factories explicitly #11 --- tests/Integration/HttpAdapter/Psr18ClientAdapterTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Integration/HttpAdapter/Psr18ClientAdapterTest.php b/tests/Integration/HttpAdapter/Psr18ClientAdapterTest.php index d42f244..c796a98 100644 --- a/tests/Integration/HttpAdapter/Psr18ClientAdapterTest.php +++ b/tests/Integration/HttpAdapter/Psr18ClientAdapterTest.php @@ -7,6 +7,7 @@ use Marein\Nchan\Http\Client; use Marein\Nchan\Http\Request; use Marein\Nchan\HttpAdapter\Psr18ClientAdapter; +use Nyholm\Psr7\Factory\Psr17Factory; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\Psr18Client; @@ -14,7 +15,7 @@ class Psr18ClientAdapterTest extends ClientTestCase { protected function createClient(Request $request): Client { - $client = new Psr18Client(HttpClient::create()); + $client = new Psr18Client(HttpClient::create(), new Psr17Factory(), new Psr17Factory()); return new Psr18ClientAdapter( $client, From a955ca24781cd77924183f2b205e91cf3b8ce762 Mon Sep 17 00:00:00 2001 From: Markus Reinhold Date: Mon, 14 Mar 2022 22:00:55 +0100 Subject: [PATCH 4/8] Collapse code samples Not related, just an improvement. --- README.md | 156 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 88 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 04a4e70..e8c51da 100644 --- a/README.md +++ b/README.md @@ -36,117 +36,137 @@ you must enable the php configuration ### Publish a message -```php - + Show code -namespace { + ```php + channel('/path-to-publisher-endpoint'); - $channelInformation = $channel->publish( - new PlainTextMessage( - 'my-message-name', - 'my message content' - ) - ); + include '/path/to/autoload.php'; - // Nchan returns some channel information after publishing a message. - var_dump($channelInformation); -} -``` + $nchan = new Nchan('http://my-nchan-domain'); + $channel = $nchan->channel('/path-to-publisher-endpoint'); + $channelInformation = $channel->publish( + new PlainTextMessage( + 'my-message-name', + 'my message content' + ) + ); + + // Nchan returns some channel information after publishing a message. + var_dump($channelInformation); + } + ``` + ### Get channel information -```php - + Show code -namespace { + ```php + channel('/path-to-publisher-endpoint'); - $channelInformation = $channel->information(); + include '/path/to/autoload.php'; - var_dump($channelInformation); -} -``` + $nchan = new Nchan('http://my-nchan-domain'); + $channel = $nchan->channel('/path-to-publisher-endpoint'); + $channelInformation = $channel->information(); + + var_dump($channelInformation); + } + ``` + ### Delete a channel -```php - + Show code -namespace { + ```php + channel('/path-to-publisher-endpoint'); - $channel->delete(); -} -``` + include '/path/to/autoload.php'; + + $nchan = new Nchan('http://my-nchan-domain'); + $channel = $nchan->channel('/path-to-publisher-endpoint'); + $channel->delete(); + } + ``` + ### Nchan status information First you have to create a location with the `nchan_stub_status directive`. Then you can query it. -```php - + Show code + + ```php + status('/path-to-status-location'); - $statusInformation = $status->information(); + $nchan = new Nchan('http://my-nchan-domain'); + $status = $nchan->status('/path-to-status-location'); + $statusInformation = $status->information(); - var_dump($statusInformation); -} + var_dump($statusInformation); + } ``` + ### Use with authentication Nchan gives you the possibility to authenticate endpoints with the `nchan_authorize_request` directive. The provided http client supports basic and bearer authentication. It needs to be setup as follows. -```php - + Show code -namespace { + ```php + The `\Marein\Nchan\HttpAdapter\HttpStreamWrapperClient` From 53459be7b68ca72c1e0a7b1b0831fdd79f1c37be Mon Sep 17 00:00:00 2001 From: Markus Reinhold Date: Sun, 20 Mar 2022 23:31:28 +0100 Subject: [PATCH 5/8] Mention PSR-18 adapter in README.md --- README.md | 98 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index e8c51da..5d7b365 100644 --- a/README.md +++ b/README.md @@ -12,28 +12,33 @@ __Table of contents__ * [Delete a channel](#delete-a-channel) * [Nchan status information](#nchan-status-information) * [Use with authentication](#use-with-authentication) -* [Exchange the provided http client](#exchange-the-provided-http-client) +* [PSR-18 compatibility](#psr-18-compatibility) ## Overview This is a PHP client for [https://nchan.io](https://nchan.io). -This library provides a http client which has some authentication features. If you need more, you can for sure -exchange this library with another like guzzle. Take a look below to -"[Exchange the provided http client](#exchange-the-provided-http-client)". - ## Installation and requirements ``` composer require marein/php-nchan-client ``` -If you use the provided http client (default if you don't set anything), +If you want to use the +[PSR-18 adapter](#psr-18-compatibility), +install a library that implements PSR-18 http client +([see here](https://packagist.org/providers/psr/http-client-implementation)) +and a library that implements PSR-17 http factories +([see here](https://packagist.org/providers/psr/http-factory-implementation)). + +If you want to use the built-in http client (default if you don't set anything), you must enable the php configuration [allow_url_fopen](http://php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen). ## Usage +The following code examples use the built-in http client. + ### Publish a message
@@ -177,16 +182,75 @@ method. Take a look at `\Marein\Nchan\HttpAdapter\BasicAuthenticationCredentials` to see how this works. -## Exchange the provided http client +## PSR-18 compatibility + +This library comes with a PSR-18 compatible +[adapter](/src/HttpAdapter/Psr18ClientAdapter.php). +There are good reasons not to use the built-in client. +It's based on the http stream wrapper and `file_get_contents`. +This closes the TCP connection after each request. +Other clients, see below, can keep the connection open. + +The following example uses +[guzzlehttp/guzzle](https://packagist.org/packages/guzzlehttp/guzzle) +and +[guzzlehttp/psr7](https://packagist.org/packages/guzzlehttp/psr7). + +
+ Show code + + ```php + + +The following code example uses +[symfony/http-client](https://packagist.org/packages/symfony/http-client) +and +[nyholm/psr7](https://packagist.org/packages/nyholm/psr7). -Sometimes, the provided client is not enough and you want to use features from other libraries like guzzle. -You can exchange the http client easily because of the -`\Marein\Nchan\Http\Client` -interface. I've created a guzzle adapter -for those who want to use guzzle. This is also a good example to look at, if you want to use another library. The -guzzle adapter lives at -[marein/php-nchan-client-guzzle-adapter](https://github.com/marein/php-nchan-client-guzzle-adapter). +
+ Show code + + ```php + From 8bba2fca6acfde7ade2a91c5cac3f78db27d8940 Mon Sep 17 00:00:00 2001 From: Markus Reinhold Date: Mon, 21 Mar 2022 21:38:23 +0100 Subject: [PATCH 6/8] Change wording in documentation #11 --- README.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 5d7b365..0d69024 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ __Table of contents__ * [Get channel information](#get-channel-information) * [Delete a channel](#delete-a-channel) * [Nchan status information](#nchan-status-information) - * [Use with authentication](#use-with-authentication) + * [Authorize requests](#authorize-requests) * [PSR-18 compatibility](#psr-18-compatibility) ## Overview @@ -32,7 +32,7 @@ and a library that implements PSR-17 http factories ([see here](https://packagist.org/providers/psr/http-factory-implementation)). If you want to use the built-in http client (default if you don't set anything), -you must enable the php configuration +enable the php configuration [allow_url_fopen](http://php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen). ## Usage @@ -115,7 +115,7 @@ The following code examples use the built-in http client. ### Nchan status information -First you have to create a location with the `nchan_stub_status directive`. Then you can query it. +Endpoints with the `nchan_stub_status` directive can be queried as follows.
Show code @@ -138,10 +138,17 @@ First you have to create a location with the `nchan_stub_status directive`. Then ```
-### Use with authentication +### Authorize requests -Nchan gives you the possibility to authenticate endpoints with the `nchan_authorize_request` directive. -The provided http client supports basic and bearer authentication. It needs to be setup as follows. +Endpoints with the `nchan_authorize_request` directive must be authorized. +The constructor of the +[built-in http client](/src/HttpAdapter/HttpStreamWrapperClient.php) +takes an implementation of type +[Credentials](/src/HttpAdapter/Credentials.php). +This library comes with 2 built-in implementations, +[BasicAuthenticationCredentials](/src/HttpAdapter/BasicAuthenticationCredentials.php) +and +[BearerAuthenticationCredentials](/src/HttpAdapter/BearerAuthenticationCredentials.php).
Show code @@ -173,14 +180,9 @@ The provided http client supports basic and bearer authentication. It needs to b ```
-The -`\Marein\Nchan\HttpAdapter\HttpStreamWrapperClient` -class constructor takes an implementation of type -`\Marein\Nchan\HttpAdapter\Credentials`. -As long as you implement that interface, you can build your own authentication -method. Take a look at -`\Marein\Nchan\HttpAdapter\BasicAuthenticationCredentials` -to see how this works. +If you use another http client through the +[PSR-18 adapter](#psr-18-compatibility), +the respective http client has its own extension points to modify the request before it is sent. ## PSR-18 compatibility From b11c110f4a615657a364c9e3755f4dfe5f39f96b Mon Sep 17 00:00:00 2001 From: Markus Reinhold Date: Mon, 21 Mar 2022 23:10:27 +0100 Subject: [PATCH 7/8] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d69024..daeb5af 100644 --- a/README.md +++ b/README.md @@ -158,9 +158,9 @@ and namespace { - use Marein\Nchan\HttpAdapter\HttpStreamWrapperClient; use Marein\Nchan\HttpAdapter\BasicAuthenticationCredentials; use Marein\Nchan\HttpAdapter\BearerAuthenticationCredentials; + use Marein\Nchan\HttpAdapter\HttpStreamWrapperClient; use Marein\Nchan\Nchan; include '/path/to/autoload.php'; From 277e9106faeba383739f8e359ad22f15dc58279b Mon Sep 17 00:00:00 2001 From: Markus Reinhold Date: Tue, 22 Mar 2022 22:51:42 +0100 Subject: [PATCH 8/8] Apply same code style in examples --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index daeb5af..8c17130 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,9 @@ and ```php @@ -231,6 +235,9 @@ and ```php