The project is an API that will itself make HTTP requests to external webservices.
Since this is PHP, we actually chose to use guzzlehttp. Now the library itself doesn't matter much, but I'm scared we'll be stuck with it if it ever has a major change (it already happened in the past).
So I thought I'd create an interface that would be common to be able to swap the implementation if needed. Here it is (I removed the Use
statements for clarity) :
<?php
namespace App\Core\Request;
interface HttpRequest
{
public function get();
public function post();
public function setUri($uri);
public function setDatas(array $datas);
public function setHeaders(array $headers);
}
I thought this would be usable by cURL if we need it.
Then I made the Guzzle implementation, here it is :
<?php
namespace App\Core\Request;
class GuzzleHttpRequest implements HttpRequest
{
protected $client;
protected $logger;
protected $datas = [];
protected $headers = [];
protected $uri;
protected $requestLog;
protected $currentRequestLog;
protected $serviceId;
const DEFAULT_ERROR_CODE = 500;
public function __construct(Client $client, GuzzleLogger $logger, RequestsLog $requestLog)
{
$this->client = $client;
$this->logger = $logger;
$this->requestLog = $requestLog;
$this->serviceId = Config::get('app.locale');
}
public function setDatas(array $datas)
{
$this->datas = $datas;
}
public function setHeaders(array $headers)
{
$this->headers = $headers;
}
public function get()
{
return $this->request('GET', 'query');
}
public function post()
{
return $this->request('POST', 'json');
}
protected function request($method, $dataType)
{
try {
$response = $this->sendRequest($method, $dataType);
return $this->handleResponse($response);
}
catch (RequestException $e) {
return $this->handleRequestException($e);
}
catch (Exception $e) {
return $this->handleException($e);
}
}
protected function sendRequest($method, $dataType)
{
$this->checkUri();
$this->currentRequestLog = $this->requestLog->fromConnection($this->serviceId, $this->getUri());
return $this->client->request($method, $this->getUri(), $this->getOptions($dataType));
}
protected function checkUri()
{
if (is_null($this->getUri())) {
throw new UriNotSetException('Uri should be set.', self::DEFAULT_ERROR_CODE);
}
}
protected function getUri()
{
return $this->uri;
}
public function setUri($uri)
{
$this->uri = $uri;
}
protected function getOptions($dataType)
{
return [$dataType => $this->datas, 'headers' => $this->headers, 'handler' => $this->getHandler('generic')];
}
protected function getHandler($logName)
{
$uniqId = uniqid('REQUEST');
return $this->logger->createLoggingHandlerStack([
"REQUEST_LOG_ID_{$this->currentRequestLog->id} REQUEST : \r\n {request}",
"REQUEST_LOG_ID_{$this->currentRequestLog->id} RESPONSE : {response}",
], $logName);
}
protected function handleResponse(GuzzleResponse $response)
{
$this->currentRequestLog->succeed();
return ResponseJsonSuccess::getResponse($this->getFormattedResponse($response));
}
protected function getFormattedResponse(GuzzleResponse $response)
{
return (string)$response->getBody();
}
protected function handleRequestException(RequestException $e)
{
$this->currentRequestLog->fail();
$code = $e->getCode() ?: self::DEFAULT_ERROR_CODE;
$message = (string)$e->getMessage();
return $this->handleException(new Exception($message, $code));
}
protected function handleException(Exception $e)
{
return ResponseJsonError::getResponse($e);
}
}
This class could be problematic to me as it's doing lots of things. It's able to handle requests and responses (and exceptions bound to these), and it also logs all of its requests.
I did this class to be able to implement subclasses to change the behavior when needed, but I still wanted to be able to use this main class for most requests.
Here's an example of a subclass I made :
<?php
namespace App\Services\Pivotal\Request;
class PivotalGuzzleHttpRequest extends GuzzleHttpRequest
{
private $tokenFactory;
public function __construct(Client $client, GuzzleLogger $logger, RequestsLog $requestLog, TokenFactory $tokenFactory)
{
parent::__construct($client, $logger, $requestLog);
$this->tokenFactory = $tokenFactory;
}
protected function getFormattedResponse(Response $response)
{
$this->currentRequestLog->succeed();
return json_decode((string)$response->getBody(), true);
}
protected function getOptions($dataType)
{
$options = parent::getOptions($dataType);
$options['headers'] = [
"Content-Type" => "application/json",
"Authorization" => "Bearer {$this->tokenFactory->getTokenId()}"
];
$options['handler'] = $this->getHandler('pivotal');
return $options;
}
}
How can I improve this code ? All I'm looking for is the code to be easy to use, easy to test, and easy to understand.
use
statement for clarity when they typically add clarity? \$\endgroup\$