Skip to content

Commit

Permalink
Add Psr18Client to make it straightforward to use PSR-18 (#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-grekas authored Apr 26, 2023
1 parent d35947f commit 07b7cc3
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
49 changes: 43 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```


Expand Down
45 changes: 45 additions & 0 deletions src/Psr18Client.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Http\Discovery;

use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UriFactoryInterface;

/**
* A generic PSR-18 and PSR-17 implementation.
*
* You can create this class with concrete client and factory instances
* or let it use discovery to find suitable implementations as needed.
*
* @author Nicolas Grekas <[email protected]>
*/
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);
}
}
92 changes: 92 additions & 0 deletions tests/Psr18ClientTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace tests\Http\Discovery;

use Http\Discovery\Psr17Factory;
use Http\Discovery\Psr18Client;
use Http\Discovery\Psr18ClientDiscovery;
use Http\Discovery\Strategy\DiscoveryStrategy;
use PHPUnit\Framework\TestCase;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class Psr18ClientTest extends TestCase
{
protected function setUp(): void
{
if (!interface_exists(RequestFactoryInterface::class)) {
$this->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);
}
}

0 comments on commit 07b7cc3

Please sign in to comment.