Skip to content

Add Psr18Client to make it straightforward to use PSR-18 #230

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <p@tchwork.com>
*/
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);
}
}