Skip to content

Instantly share code, notes, and snippets.

@collegeman
Last active January 2, 2020 18:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save collegeman/ed7969038cece0d0db57bff451bef7bf to your computer and use it in GitHub Desktop.
Save collegeman/ed7969038cece0d0db57bff451bef7bf to your computer and use it in GitHub Desktop.
Using a trait to add an API client to any PHP class

Using a trait to add an API client to any PHP class

<?php
namespace Tests\Feature;
use App\Concerns\MakesRequests;
class HttpBinClient
{
use MakesRequests;
protected $config = [
'base_uri' => 'https://httpbin.org',
];
}
<?php
namespace App\Adapters;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
class HttpResponse implements ResponseInterface
{
/**
* @var ResponseInterface
*/
protected $proxied;
/**
* Contains parsed JSON content
* @var mixed
*/
protected $parsedJsonContent;
function __construct(ResponseInterface $response)
{
$this->proxied = $response;
}
public function containsJson()
{
if ($this->proxied->hasHeader('Content-Type')) {
$contentType = $this->proxied->getHeader('Content-Type');
return in_array('application/json', $contentType)
|| in_array('application/json; charset=utf-8', $contentType);
}
return false;
}
/**
* @return $this
*/
public function parseJson()
{
if (is_null($this->parsedJsonContent)) {
if ($this->containsJson()) {
$this->parsedJsonContent = json_decode($this->proxied->getBody()->getContents());
}
}
return $this;
}
public function json($key = null)
{
$json = $this->parseJson()->parsedJsonContent;
return $key ? data_get($json, $key) : $json;
}
/**
* @param null $key
* @return \Illuminate\Support\Collection
*/
public function collect($key = null)
{
return collect($key ? data_get($this->json(), $key) : $this->json());
}
public function getProtocolVersion()
{
return $this->proxied->getProtocolVersion();
}
public function withProtocolVersion($version)
{
return $this->withProtocolVersion($version);
}
public function getHeaders()
{
return $this->proxied->getHeaders();
}
public function hasHeader($name)
{
return $this->proxied->hasHeader($name);
}
public function getHeader($name)
{
return $this->proxied->getHeader($name);
}
public function getHeaderLine($name)
{
return $this->proxied->getHeaderLine($name);
}
public function withHeader($name, $value)
{
return $this->proxied->withHeader($name, $value);
}
public function withAddedHeader($name, $value)
{
return $this->proxied->withAddedHeader($name, $value);
}
public function withoutHeader($name)
{
return $this->proxied->withoutHeader($name);
}
public function getBody()
{
return $this->proxied->getBody();
}
public function withBody(StreamInterface $body)
{
return $this->proxied->withBody($body);
}
public function getStatusCode()
{
return $this->proxied->getStatusCode();
}
public function withStatus($code, $reasonPhrase = '')
{
return $this->proxied->withStatus($code, $reasonPhrase);
}
public function getReasonPhrase()
{
return $this->proxied->getReasonPhrase();
}
}
<?php
namespace App\Concerns;
use App\Adapters\HttpResponse;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Response;
trait MakesRequests
{
/**
* @var Client
*/
protected $httpClient = null;
/**
* @param Client $client
*/
function setHttpClient(Client $client)
{
$this->httpClient = $client;
}
/**
* @return Client
*/
function getHttpClient()
{
if (is_null($this->httpClient)) {
$this->httpClient = new Client($this->getHttpConfig());
}
return $this->httpClient;
}
/**
* @return array
*/
function getHttpConfig()
{
$config = [];
if (isset($this->httpConfig)) {
$config = $this->httpConfig;
} else if (isset($this->config)) {
$config = $this->config;
}
return $config;
}
/**
* Make an HTTP request.
* @param $method
* @param $args
* @return HttpResponse
* @throws RequestException
* @throws \Throwable
*/
function __call($method, $args)
{
return $this->request($method, $args);
}
/**
* @param $method
* @param $args
* @return HttpResponse
* @throws \Throwable
*/
function request($method, $args)
{
$method = strtoupper(trim($method));
$uri = $args[0] ?? '';
$options = $args[1] ?? [];
try {
$response = $this->getHttpClient()->request($method, $uri, $options);
} catch (\Throwable $e) {
$this->handleError($e);
}
return $this->handleResponse($response);
}
/**
* @param \Throwable $e
* @return mixed
* @throws \Throwable
*/
function handleError(\Throwable $e)
{
throw $e;
}
/**
* @param Response $response
* @return HttpResponse
*/
function handleResponse(Response $response)
{
return new HttpResponse($response);
}
/**
* Retry an operation a given number of times, allowing
* for 500, 502, 503, 504, 507, and 508 response codes
* and denying retries for all others, e.g., 404.
*
* @param int $times
* @param callable $callback
* @param int $sleep
* @param callable $when
* @return mixed
*
* @throws \Exception
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
*/
function retry($times, callable $callback, $sleep = 0, $when = null)
{
// only certain types of HTTP responses
$allow = [500, 502, 503, 504, 507, 508];
return retry($times, $callback, $sleep, function($e) use ($allow, $when) {
// allow individual invocations to override $when behavior
if ($when && !$when($e)) {
// don't allow retry
return false;
}
// only retry certain status codes
if ($e instanceof RequestException) {
if ($e->getResponse() && !in_array($e->getCode(), $allow)) {
// don't allow retry
return false;
}
}
// allow retry
return true;
});
}
}
<?php
namespace Tests\Feature;
use Tests\TestCase;
class RequestsTests extends TestCase
{
/**
* A basic feature test example.
*
* @return void
* @group requests
*/
public function testBasicRequests()
{
$httpBin = new HttpBinClient();
$response = $httpBin->post('/anything', [
'form_params' => [
'foo' => 'bar',
]
])->json();
$this->assertTrue($response instanceof \stdClass);
$this->assertEquals('bar', data_get($response, 'form.foo'));
$response = $httpBin->post('/anything', [
'json' => [
'bing' => 'cherry',
]
])->json();
$this->assertEquals('cherry', data_get($response, 'json.bing'));
$response = $httpBin->get('/anything?query=arg')->json();
$this->assertEquals('arg', data_get($response, 'args.query'));
$response = $httpBin->get('/anything', [
'query' => [
'query' => 'arg',
]
])->json();
$this->assertEquals('arg', data_get($response, 'args.query'));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment