Skip to content

Commit 381c71b

Browse files
Add Psr18Client to make it straightforward to use PSR-18
1 parent d35947f commit 381c71b

File tree

4 files changed

+184
-6
lines changed

4 files changed

+184
-6
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log
22

3+
## 1.17.0 - 2023-XX-XX
4+
5+
- [#230](https://github.com/php-http/discovery/pull/230) - Add Psr18Client to make it straightforward to use PSR-18
6+
37
## 1.16.0 - 2023-04-26
48

59
- [#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

README.md

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,70 @@
22

33
[![Latest Version](https://img.shields.io/github/release/php-http/discovery.svg?style=flat-square)](https://github.com/php-http/discovery/releases)
44
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
5-
[![Build Status](https://img.shields.io/travis/php-http/discovery/master.svg?style=flat-square)](https://travis-ci.org/php-http/discovery)
5+
[![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)
66
[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/discovery.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/discovery)
77
[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/discovery.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/discovery)
88
[![Total Downloads](https://img.shields.io/packagist/dt/php-http/discovery.svg?style=flat-square)](https://packagist.org/packages/php-http/discovery)
99

10-
**Find installed PSR-17 factories, PSR-18 clients and HTTPlug factories.**
10+
**This library provides auto-discovery and auto-installation of well-known PSR-17, PSR-18 and HTTPlug implementations.**
1111

12-
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.
1312

1413
## Install
1514

1615
Via Composer
1716

1817
``` bash
19-
$ composer require php-http/discovery
18+
composer require php-http/discovery
2019
```
2120

2221

23-
## Documentation
22+
## Usage
2423

2524
Please see the [official documentation](http://php-http.readthedocs.org/en/latest/discovery.html).
2625

26+
If your library/SDK needs a PSR-18 client, here is a quick example.
27+
28+
First, you need to install a PSR-18 client and a PSR-17 factory implementations. This should
29+
be done only for dev dependencies as you don't want to force a specific one on your users:
30+
31+
```bash
32+
composer require --dev symfony/http-client
33+
composer require --dev nyholm/psr7
34+
```
35+
36+
Then, you can disable the Composer plugin embeded in `php-http/discovery`
37+
because you just installed the dev dependencies you need for testing:
38+
39+
```bash
40+
composer config allow-plugins.php-http/discovery false
41+
```
42+
43+
Finally, you need to require `php-http/discovery` and the generic implementations that
44+
your library is going to need:
45+
46+
```bash
47+
composer require php-http/discovery:^1.17
48+
composer require psr/http-client-implementation:*
49+
composer require psr/http-factory-implementation:*
50+
```
51+
52+
Now, you're ready to make an HTTP request:
53+
54+
```php
55+
use Http\Discovery\Psr18Client;
56+
57+
$client = new Psr18Client();
58+
59+
$request = $client->createRequest('GET', 'https://example.com');
60+
$response = $client->sendRequest($request);
61+
```
62+
63+
Internally, this code will use whatever PSR-7, PSR-17 and PSR-18 implementations that your users have installed.
2764

2865
## Testing
2966

3067
``` bash
31-
$ composer test
68+
composer test
3269
```
3370

3471

src/Psr18Client.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Http\Discovery;
4+
5+
use Psr\Http\Client\ClientInterface;
6+
use Psr\Http\Message\RequestFactoryInterface;
7+
use Psr\Http\Message\RequestInterface;
8+
use Psr\Http\Message\ResponseFactoryInterface;
9+
use Psr\Http\Message\ResponseInterface;
10+
use Psr\Http\Message\ServerRequestFactoryInterface;
11+
use Psr\Http\Message\StreamFactoryInterface;
12+
use Psr\Http\Message\UploadedFileFactoryInterface;
13+
use Psr\Http\Message\UriFactoryInterface;
14+
15+
/**
16+
* A generic PSR-18 and PSR-17 implementation.
17+
*
18+
* You can create this class with concrete client and factory instances
19+
* or let it use discovery to find suitable implementations as needed.
20+
*
21+
* @author Nicolas Grekas <p@tchwork.com>
22+
*/
23+
class Psr18Client extends Psr17Factory implements ClientInterface
24+
{
25+
private $client;
26+
27+
public function __construct(
28+
ClientInterface $client = null,
29+
RequestFactoryInterface $requestFactory = null,
30+
ResponseFactoryInterface $responseFactory = null,
31+
ServerRequestFactoryInterface $serverRequestFactory = null,
32+
StreamFactoryInterface $streamFactory = null,
33+
UploadedFileFactoryInterface $uploadedFileFactory = null,
34+
UriFactoryInterface $uriFactory = null
35+
) {
36+
parent::__construct($requestFactory, $responseFactory, $serverRequestFactory, $streamFactory, $uploadedFileFactory, $uriFactory);
37+
38+
$this->client = $client ?? Psr18ClientDiscovery::find();
39+
}
40+
41+
public function sendRequest(RequestInterface $request): ResponseInterface
42+
{
43+
return $this->client->sendRequest($request);
44+
}
45+
}

tests/Psr18ClientTest.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
namespace tests\Http\Discovery;
4+
5+
use Http\Discovery\Psr17Factory;
6+
use Http\Discovery\Psr18Client;
7+
use Http\Discovery\Psr18ClientDiscovery;
8+
use Http\Discovery\Strategy\DiscoveryStrategy;
9+
use PHPUnit\Framework\TestCase;
10+
use Psr\Http\Client\ClientInterface;
11+
use Psr\Http\Message\RequestFactoryInterface;
12+
use Psr\Http\Message\RequestInterface;
13+
use Psr\Http\Message\ResponseInterface;
14+
15+
class Psr18ClientTest extends TestCase
16+
{
17+
protected function setUp(): void
18+
{
19+
if (!interface_exists(RequestFactoryInterface::class)) {
20+
$this->markTestSkipped(RequestFactoryInterface::class.' required.');
21+
}
22+
if (!interface_exists(ClientInterface::class)) {
23+
$this->markTestSkipped(ClientInterface::class.' required.');
24+
}
25+
}
26+
27+
public function testClient()
28+
{
29+
$mockClient = new class() implements ClientInterface {
30+
public $request;
31+
public $response;
32+
33+
public function sendRequest(RequestInterface $request): ResponseInterface
34+
{
35+
$this->request = $request;
36+
37+
return $this->response;
38+
}
39+
};
40+
41+
$client = new Psr18Client($mockClient);
42+
$this->assertInstanceOf(Psr17Factory::class, $client);
43+
44+
$mockResponse = $client->createResponse();
45+
$mockClient->response = $mockResponse;
46+
47+
$request = $client->createRequest('GET', '/foo');
48+
$this->assertSame($mockResponse, $client->sendRequest($request));
49+
$this->assertSame($request, $mockClient->request);
50+
}
51+
52+
public function testDiscovery()
53+
{
54+
$mockClient = new class() implements ClientInterface, DiscoveryStrategy {
55+
public static $client;
56+
57+
public $request;
58+
public $response;
59+
60+
public function __construct()
61+
{
62+
self::$client = $this;
63+
}
64+
65+
public function sendRequest(RequestInterface $request): ResponseInterface
66+
{
67+
$this->request = $request;
68+
69+
return $this->response;
70+
}
71+
72+
public static function getCandidates($type)
73+
{
74+
return is_a(ClientInterface::class, $type, true)
75+
? [['class' => self::class, 'condition' => self::class]]
76+
: [];
77+
}
78+
};
79+
80+
Psr18ClientDiscovery::prependStrategy(get_class($mockClient));
81+
$client = new Psr18Client();
82+
83+
$this->assertInstanceOf(get_class($mockClient), $mockClient::$client);
84+
$mockClient = $mockClient::$client;
85+
$mockResponse = $client->createResponse();
86+
$mockClient->response = $mockResponse;
87+
88+
$request = $client->createRequest('GET', '/foo');
89+
$this->assertSame($mockResponse, $client->sendRequest($request));
90+
$this->assertSame($request, $mockClient->request);
91+
}
92+
}

0 commit comments

Comments
 (0)