From 381c71b6601ff292abe0565e5282e0e45f570e6b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 26 Apr 2023 11:25:32 +0200 Subject: [PATCH] Add Psr18Client to make it straightforward to use PSR-18 --- CHANGELOG.md | 4 ++ README.md | 49 ++++++++++++++++++--- src/Psr18Client.php | 45 +++++++++++++++++++ tests/Psr18ClientTest.php | 92 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 src/Psr18Client.php create mode 100644 tests/Psr18ClientTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index dad161d..476d649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 1.17.0 - 2023-XX-XX + +- [#230](https://github.com/php-http/discovery/pull/230) - Add Psr18Client to make it straightforward to use PSR-18 + ## 1.16.0 - 2023-04-26 - [#225](https://github.com/php-http/discovery/pull/225) - Remove support for the abandoned Zend Diactoros which has been replaced with Laminas Diactoros; marked the zend library as conflict in composer.json to avoid confusion diff --git a/README.md b/README.md index 25a6884..51708e7 100644 --- a/README.md +++ b/README.md @@ -2,33 +2,70 @@ [![Latest Version](https://img.shields.io/github/release/php-http/discovery.svg?style=flat-square)](https://github.com/php-http/discovery/releases) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) -[![Build Status](https://img.shields.io/travis/php-http/discovery/master.svg?style=flat-square)](https://travis-ci.org/php-http/discovery) +[![Tests](https://github.com/php-http/discovery/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/php-http/discovery/actions/workflows/ci.yml?query=branch%3Amaster) [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/discovery.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/discovery) [![Quality Score](https://img.shields.io/scrutinizer/g/php-http/discovery.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/discovery) [![Total Downloads](https://img.shields.io/packagist/dt/php-http/discovery.svg?style=flat-square)](https://packagist.org/packages/php-http/discovery) -**Find installed PSR-17 factories, PSR-18 clients and HTTPlug factories.** +**This library provides auto-discovery and auto-installation of well-known PSR-17, PSR-18 and HTTPlug implementations.** -Since 1.15.0, this library also provides a composer plugin that automatically installs well-known PSR implementations if composer dependencies require a PSR implementation but do not specify which implementation to install. ## Install Via Composer ``` bash -$ composer require php-http/discovery +composer require php-http/discovery ``` -## Documentation +## Usage Please see the [official documentation](http://php-http.readthedocs.org/en/latest/discovery.html). +If your library/SDK needs a PSR-18 client, here is a quick example. + +First, you need to install a PSR-18 client and a PSR-17 factory implementations. This should +be done only for dev dependencies as you don't want to force a specific one on your users: + +```bash +composer require --dev symfony/http-client +composer require --dev nyholm/psr7 +``` + +Then, you can disable the Composer plugin embeded in `php-http/discovery` +because you just installed the dev dependencies you need for testing: + +```bash +composer config allow-plugins.php-http/discovery false +``` + +Finally, you need to require `php-http/discovery` and the generic implementations that +your library is going to need: + +```bash +composer require php-http/discovery:^1.17 +composer require psr/http-client-implementation:* +composer require psr/http-factory-implementation:* +``` + +Now, you're ready to make an HTTP request: + +```php +use Http\Discovery\Psr18Client; + +$client = new Psr18Client(); + +$request = $client->createRequest('GET', 'https://example.com'); +$response = $client->sendRequest($request); +``` + +Internally, this code will use whatever PSR-7, PSR-17 and PSR-18 implementations that your users have installed. ## Testing ``` bash -$ composer test +composer test ``` diff --git a/src/Psr18Client.php b/src/Psr18Client.php new file mode 100644 index 0000000..c47780e --- /dev/null +++ b/src/Psr18Client.php @@ -0,0 +1,45 @@ + + */ +class Psr18Client extends Psr17Factory implements ClientInterface +{ + private $client; + + public function __construct( + ClientInterface $client = null, + RequestFactoryInterface $requestFactory = null, + ResponseFactoryInterface $responseFactory = null, + ServerRequestFactoryInterface $serverRequestFactory = null, + StreamFactoryInterface $streamFactory = null, + UploadedFileFactoryInterface $uploadedFileFactory = null, + UriFactoryInterface $uriFactory = null + ) { + parent::__construct($requestFactory, $responseFactory, $serverRequestFactory, $streamFactory, $uploadedFileFactory, $uriFactory); + + $this->client = $client ?? Psr18ClientDiscovery::find(); + } + + public function sendRequest(RequestInterface $request): ResponseInterface + { + return $this->client->sendRequest($request); + } +} diff --git a/tests/Psr18ClientTest.php b/tests/Psr18ClientTest.php new file mode 100644 index 0000000..be794d0 --- /dev/null +++ b/tests/Psr18ClientTest.php @@ -0,0 +1,92 @@ +markTestSkipped(RequestFactoryInterface::class.' required.'); + } + if (!interface_exists(ClientInterface::class)) { + $this->markTestSkipped(ClientInterface::class.' required.'); + } + } + + public function testClient() + { + $mockClient = new class() implements ClientInterface { + public $request; + public $response; + + public function sendRequest(RequestInterface $request): ResponseInterface + { + $this->request = $request; + + return $this->response; + } + }; + + $client = new Psr18Client($mockClient); + $this->assertInstanceOf(Psr17Factory::class, $client); + + $mockResponse = $client->createResponse(); + $mockClient->response = $mockResponse; + + $request = $client->createRequest('GET', '/foo'); + $this->assertSame($mockResponse, $client->sendRequest($request)); + $this->assertSame($request, $mockClient->request); + } + + public function testDiscovery() + { + $mockClient = new class() implements ClientInterface, DiscoveryStrategy { + public static $client; + + public $request; + public $response; + + public function __construct() + { + self::$client = $this; + } + + public function sendRequest(RequestInterface $request): ResponseInterface + { + $this->request = $request; + + return $this->response; + } + + public static function getCandidates($type) + { + return is_a(ClientInterface::class, $type, true) + ? [['class' => self::class, 'condition' => self::class]] + : []; + } + }; + + Psr18ClientDiscovery::prependStrategy(get_class($mockClient)); + $client = new Psr18Client(); + + $this->assertInstanceOf(get_class($mockClient), $mockClient::$client); + $mockClient = $mockClient::$client; + $mockResponse = $client->createResponse(); + $mockClient->response = $mockResponse; + + $request = $client->createRequest('GET', '/foo'); + $this->assertSame($mockResponse, $client->sendRequest($request)); + $this->assertSame($request, $mockClient->request); + } +}