Skip to content

Opening the possibility of injecting a PSR-18 compatible HTTP Client #33

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 5 commits into from
Jun 14, 2020
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,20 @@ $client = new Client(
);
```


It is possible to use your own preconfigured HTTP client that implements the [PSR-18 interface](https://www.php-fig.org/psr/psr-18/).

Example:

```php
$client = new Client(
'http://api.graphql.com',
[],
[],
$myHttpClient
);
```

# Running Queries

## Result Formatting
Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
"require": {
"php": "^7.1",
"ext-json": "*",
"psr/http-message": "^1.0",
"psr/http-client": "^1.0",
"guzzlehttp/guzzle": "^6.3"
},
"require-dev": {
Expand Down
58 changes: 31 additions & 27 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

use GraphQL\Exception\QueryError;
use GraphQL\QueryBuilder\QueryBuilderInterface;
use GraphQL\Util\GuzzleAdapter;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Client\ClientInterface;
use TypeError;
use GuzzleHttp\Psr7;

/**
* Class Client
Expand All @@ -20,34 +24,41 @@ class Client
protected $endpointUrl;

/**
* @var array
*/
protected $authorizationHeaders;

/**
* @var \GuzzleHttp\Client
* @var ClientInterface
*/
protected $httpClient;

/**
* @var array
*/
protected $httpOptions;

protected $httpHeaders;

/**
* Client constructor.
*
* @param string $endpointUrl
* @param array $authorizationHeaders
* @param array $httpOptions
* @param ClientInterface $httpClient
*/
public function __construct(string $endpointUrl, array $authorizationHeaders = [], array $httpOptions = [])
public function __construct(string $endpointUrl, array $authorizationHeaders = [], array $httpOptions = [], ClientInterface $httpClient = null)
{
$headers = array_merge(
$authorizationHeaders,
$httpOptions['headers'] ?? [],
['Content-Type' => 'application/json']
);

/**
* All headers will be set on the request objects explicitly,
* Guzzle doesn't have to care about them at this point, so to avoid any conflicts
* we are removing the headers from the options
*/
unset($httpOptions['headers']);

$this->endpointUrl = $endpointUrl;
$this->authorizationHeaders = $authorizationHeaders;
$this->httpClient = new \GuzzleHttp\Client();
$this->httpOptions = $httpOptions;
$this->httpClient = $httpClient ?? new GuzzleAdapter(new \GuzzleHttp\Client($httpOptions));
$this->httpHeaders = $headers;
}

/**
Expand Down Expand Up @@ -75,33 +86,28 @@ public function runQuery($query, bool $resultsAsArray = false, array $variables
* @param string $queryString
* @param bool $resultsAsArray
* @param array $variables
* @param
*
* @return Results
* @throws QueryError
*/
public function runRawQuery(string $queryString, $resultsAsArray = false, array $variables = []): Results
{
// Set request headers for authorization and content type
if (!empty($this->authorizationHeaders)) {
$options['headers'] = $this->authorizationHeaders;
}
$request = new Request('POST', $this->endpointUrl);

// Set request options for \GuzzleHttp\Client
if (!empty($this->httpOptions)) {
$options = $this->httpOptions;
foreach($this->httpHeaders as $header => $value) {
$request = $request->withHeader($header, $value);
}

$options['headers']['Content-Type'] = 'application/json';

// Convert empty variables array to empty json object
if (empty($variables)) $variables = (object) null;
// Set query in the request body
$bodyArray = ['query' => (string) $queryString, 'variables' => $variables];
$options['body'] = json_encode($bodyArray);
$bodyArray = ['query' => (string) $queryString, 'variables' => $variables];
$request = $request->withBody(Psr7\stream_for(json_encode($bodyArray)));

// Send api request and get response
try {
$response = $this->httpClient->post($this->endpointUrl, $options);
$response = $this->httpClient->sendRequest($request);
}
catch (ClientException $exception) {
$response = $exception->getResponse();
Expand All @@ -114,8 +120,6 @@ public function runRawQuery(string $queryString, $resultsAsArray = false, array
}

// Parse response to extract results
$results = new Results($response, $resultsAsArray);

return $results;
return new Results($response, $resultsAsArray);
}
}
43 changes: 43 additions & 0 deletions src/Util/GuzzleAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace GraphQL\Util;

use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Client;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class GuzzleAdapter implements Client\ClientInterface
{
/**
* @var ClientInterface
*/
private $client;

/**
* GuzzleAdapter constructor.
*
* @param ClientInterface $client
*/
public function __construct(ClientInterface $client)
{
$this->client = $client;
}

/**
* @param RequestInterface $request
*
* @return ResponseInterface
* @throws GuzzleException
*/
public function sendRequest(RequestInterface $request): ResponseInterface
{
/**
* We are not catching and converting the guzzle exceptions to psr-18 exceptions
* for backward-compatibility sake
*/

return $this->client->send($request);
}
}
11 changes: 6 additions & 5 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use GraphQL\Exception\QueryError;
use GraphQL\QueryBuilder\QueryBuilder;
use GraphQL\RawObject;
use GraphQL\Util\GuzzleAdapter;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException;
Expand Down Expand Up @@ -41,7 +42,7 @@ protected function setUp(): void
{
$this->mockHandler = new MockHandler();
$handler = HandlerStack::create($this->mockHandler);
$this->client = new MockClient('', $handler);
$this->client = new Client('', [], ['handler' => $handler]);
}

/**
Expand All @@ -61,16 +62,16 @@ public function testConstructClient()
$mockHandler->append(new Response(200));
$mockHandler->append(new Response(200));

$client = new MockClient('', $handler);
$client = new Client('', [], ['handler' => $handler]);
$client->runRawQuery('query_string');

$client = new MockClient('', $handler, ['Authorization' => 'Basic xyz']);
$client = new Client('', ['Authorization' => 'Basic xyz'], ['handler' => $handler]);
$client->runRawQuery('query_string');

$client = new MockClient('', $handler);
$client = new Client('', [], ['handler' => $handler]);
$client->runRawQuery('query_string', false, ['name' => 'val']);

$client = new MockClient('', $handler, ['Authorization' => 'Basic xyz'], ['headers' => [ 'Authorization' => 'Basic zyx', 'User-Agent' => 'test' ]]);
$client = new Client('', ['Authorization' => 'Basic xyz'], ['handler' => $handler, 'headers' => [ 'Authorization' => 'Basic zyx', 'User-Agent' => 'test' ]]);
$client->runRawQuery('query_string');

/** @var Request $firstRequest */
Expand Down
27 changes: 0 additions & 27 deletions tests/MockClient.php

This file was deleted.