Introduce a request builder. (#217)

* Introduce a request builder.

We inject every dependency (eg RequestFactory and MultipartStreamBuilder) and we do not have to use postMultipart.

* code style

* Use uppercase on http verbs

* Added setters and use getters

* Added tests

* style
This commit is contained in:
Tobias Nyholm 2016-11-23 21:55:05 +01:00 committed by GitHub
parent 5535803031
commit 9bd6732efd
6 changed files with 192 additions and 128 deletions

View File

@ -163,7 +163,7 @@ class Domain extends HttpApi
'password' => $password, 'password' => $password,
]; ];
$response = $this->httpPostMultipart(sprintf('/v3/domains/%s/credentials', $domain), $params); $response = $this->httpPost(sprintf('/v3/domains/%s/credentials', $domain), $params);
return $this->deserializer->deserialize($response, CreateCredentialResponse::class); return $this->deserializer->deserialize($response, CreateCredentialResponse::class);
} }
@ -188,14 +188,7 @@ class Domain extends HttpApi
'password' => $pass, 'password' => $pass,
]; ];
$response = $this->httpPutMultipart( $response = $this->httpPut(sprintf('/v3/domains/%s/credentials/%s', $domain, $login), $params);
sprintf(
'/v3/domains/%s/credentials/%s',
$domain,
$login
),
$params
);
return $this->deserializer->deserialize($response, UpdateCredentialResponse::class); return $this->deserializer->deserialize($response, UpdateCredentialResponse::class);
} }

View File

@ -2,16 +2,11 @@
namespace Mailgun\Api; namespace Mailgun\Api;
use Http\Client\Common\HttpMethodsClient;
use Http\Client\Exception as HttplugException; use Http\Client\Exception as HttplugException;
use Http\Client\HttpClient; use Http\Client\HttpClient;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Discovery\StreamFactoryDiscovery;
use Http\Message\MultipartStream\MultipartStreamBuilder;
use Http\Message\RequestFactory;
use Mailgun\Assert;
use Mailgun\Deserializer\ResponseDeserializer; use Mailgun\Deserializer\ResponseDeserializer;
use Mailgun\Exception\HttpServerException; use Mailgun\Exception\HttpServerException;
use Mailgun\RequestBuilder;
use Mailgun\Resource\Api\ErrorResponse; use Mailgun\Resource\Api\ErrorResponse;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
@ -23,7 +18,7 @@ abstract class HttpApi
/** /**
* The HTTP client. * The HTTP client.
* *
* @var HttpMethodsClient * @var HttpClient
*/ */
private $httpClient; private $httpClient;
@ -33,13 +28,19 @@ abstract class HttpApi
protected $serializer; protected $serializer;
/** /**
* @param HttpClient $httpClient * @var RequestBuilder
* @param RequestFactory $requestFactory
* @param ResponseDeserializer $serializer
*/ */
public function __construct(HttpClient $httpClient, RequestFactory $requestFactory, ResponseDeserializer $deserializer) protected $requestBuilder;
/**
* @param HttpClient $httpClient
* @param RequestBuilder $requestBuilder
* @param ResponseDeserializer $deserializer
*/
public function __construct(HttpClient $httpClient, RequestBuilder $requestBuilder, ResponseDeserializer $deserializer)
{ {
$this->httpClient = new HttpMethodsClient($httpClient, $requestFactory); $this->httpClient = $httpClient;
$this->requestBuilder = $requestBuilder;
$this->deserializer = $deserializer; $this->deserializer = $deserializer;
} }
@ -78,7 +79,9 @@ abstract class HttpApi
} }
try { try {
$response = $this->httpClient->get($path, $requestHeaders); $response = $this->httpClient->sendRequest(
$this->requestBuilder->create('GET', $path, $requestHeaders)
);
} catch (HttplugException\NetworkException $e) { } catch (HttplugException\NetworkException $e) {
throw HttpServerException::networkError($e); throw HttpServerException::networkError($e);
} }
@ -100,33 +103,21 @@ abstract class HttpApi
return $this->httpPostRaw($path, $this->createJsonBody($parameters), $requestHeaders); return $this->httpPostRaw($path, $this->createJsonBody($parameters), $requestHeaders);
} }
/**
* Send a POST request with parameters encoded as multipart-stream form data.
*
* @param string $path Request path.
* @param array $parameters POST parameters to be mutipart-stream-encoded.
* @param array $requestHeaders Request headers.
*
* @return ResponseInterface
*/
protected function httpPostMultipart($path, array $parameters = [], array $requestHeaders = [])
{
return $this->doMultipart('POST', $path, $parameters, $requestHeaders);
}
/** /**
* Send a POST request with raw data. * Send a POST request with raw data.
* *
* @param string $path Request path. * @param string $path Request path.
* @param string $body Request body. * @param array|string $body Request body.
* @param array $requestHeaders Request headers. * @param array $requestHeaders Request headers.
* *
* @return ResponseInterface * @return ResponseInterface
*/ */
protected function httpPostRaw($path, $body, array $requestHeaders = []) protected function httpPostRaw($path, $body, array $requestHeaders = [])
{ {
try { try {
$response = $this->httpClient->post($path, $requestHeaders, $body); $response = $this->httpClient->sendRequest(
$this->requestBuilder->create('POST', $path, $requestHeaders, $body)
);
} catch (HttplugException\NetworkException $e) { } catch (HttplugException\NetworkException $e) {
throw HttpServerException::networkError($e); throw HttpServerException::networkError($e);
} }
@ -146,7 +137,9 @@ abstract class HttpApi
protected function httpPut($path, array $parameters = [], array $requestHeaders = []) protected function httpPut($path, array $parameters = [], array $requestHeaders = [])
{ {
try { try {
$response = $this->httpClient->put($path, $requestHeaders, $this->createJsonBody($parameters)); $response = $this->httpClient->sendRequest(
$this->requestBuilder->create('PUT', $path, $requestHeaders, $this->createJsonBody($parameters))
);
} catch (HttplugException\NetworkException $e) { } catch (HttplugException\NetworkException $e) {
throw HttpServerException::networkError($e); throw HttpServerException::networkError($e);
} }
@ -154,20 +147,6 @@ abstract class HttpApi
return $response; return $response;
} }
/**
* Send a PUT request with parameters encoded as multipart-stream form data.
*
* @param string $path Request path.
* @param array $parameters PUT parameters to be mutipart-stream-encoded.
* @param array $requestHeaders Request headers.
*
* @return ResponseInterface
*/
protected function httpPutMultipart($path, array $parameters = [], array $requestHeaders = [])
{
return $this->doMultipart('PUT', $path, $parameters, $requestHeaders);
}
/** /**
* Send a DELETE request with JSON-encoded parameters. * Send a DELETE request with JSON-encoded parameters.
* *
@ -180,7 +159,9 @@ abstract class HttpApi
protected function httpDelete($path, array $parameters = [], array $requestHeaders = []) protected function httpDelete($path, array $parameters = [], array $requestHeaders = [])
{ {
try { try {
$response = $this->httpClient->delete($path, $requestHeaders, $this->createJsonBody($parameters)); $response = $this->httpClient->sendRequest(
$this->requestBuilder->create('DELETE', $path, $requestHeaders, $this->createJsonBody($parameters))
);
} catch (HttplugException\NetworkException $e) { } catch (HttplugException\NetworkException $e) {
throw HttpServerException::networkError($e); throw HttpServerException::networkError($e);
} }
@ -188,60 +169,6 @@ abstract class HttpApi
return $response; return $response;
} }
/**
* Send a DELETE request with parameters encoded as multipart-stream form data.
*
* @param string $path Request path.
* @param array $parameters DELETE parameters to be mutipart-stream-encoded.
* @param array $requestHeaders Request headers.
*
* @return ResponseInterface
*/
protected function httpDeleteMultipart($path, array $parameters = [], array $requestHeaders = [])
{
return $this->doMultipart('DELETE', $path, $parameters, $requestHeaders);
}
/**
* Send a request with parameters encoded as multipart-stream form data.
*
* @param string $type Request type. (POST, PUT, etc.)
* @param string $path Request path.
* @param array $parameters POST parameters to be mutipart-stream-encoded.
* @param array $requestHeaders Request headers.
*
* @return ResponseInterface
*/
protected function doMultipart($type, $path, array $parameters = [], array $requestHeaders = [])
{
Assert::oneOf(
$type,
[
'DELETE',
'POST',
'PUT',
]
);
$streamFactory = StreamFactoryDiscovery::find();
$builder = new MultipartStreamBuilder($streamFactory);
foreach ($parameters as $k => $v) {
$builder->addResource($k, $v);
}
$multipartStream = $builder->build();
$boundary = $builder->getBoundary();
$request = MessageFactoryDiscovery::find()->createRequest(
$type,
$path,
['Content-Type' => 'multipart/form-data; boundary='.$boundary],
$multipartStream
);
return $this->httpClient->sendRequest($request);
}
/** /**
* Create a JSON encoded version of an array of parameters. * Create a JSON encoded version of an array of parameters.
* *

View File

@ -11,8 +11,6 @@ namespace Mailgun;
use Http\Client\Common\HttpMethodsClient; use Http\Client\Common\HttpMethodsClient;
use Http\Client\HttpClient; use Http\Client\HttpClient;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Message\RequestFactory;
use Mailgun\Connection\RestClient; use Mailgun\Connection\RestClient;
use Mailgun\Constants\ExceptionMessages; use Mailgun\Constants\ExceptionMessages;
use Mailgun\Lists\OptInHandler; use Mailgun\Lists\OptInHandler;
@ -50,9 +48,9 @@ class Mailgun
private $deserializer; private $deserializer;
/** /**
* @var RequestFactory * @var RequestBuilder
*/ */
private $requestFactory; private $requestBuilder;
/** /**
* @param string|null $apiKey * @param string|null $apiKey
@ -60,13 +58,15 @@ class Mailgun
* @param string $apiEndpoint * @param string $apiEndpoint
* @param ResponseDeserializer|null $deserializer * @param ResponseDeserializer|null $deserializer
* @param HttpClientConfigurator|null $clientConfigurator * @param HttpClientConfigurator|null $clientConfigurator
* @param RequestBuilder|null $requestBuilder
*/ */
public function __construct( public function __construct(
$apiKey = null, $apiKey = null,
HttpClient $httpClient = null, /* Deprecated, will be removed in 3.0 */ HttpClient $httpClient = null, /* Deprecated, will be removed in 3.0 */
$apiEndpoint = 'api.mailgun.net', /* Deprecated, will be removed in 3.0 */ $apiEndpoint = 'api.mailgun.net', /* Deprecated, will be removed in 3.0 */
ResponseDeserializer $deserializer = null, ResponseDeserializer $deserializer = null,
HttpClientConfigurator $clientConfigurator = null HttpClientConfigurator $clientConfigurator = null,
RequestBuilder $requestBuilder = null
) { ) {
$this->apiKey = $apiKey; $this->apiKey = $apiKey;
$this->restClient = new RestClient($apiKey, $apiEndpoint, $httpClient); $this->restClient = new RestClient($apiKey, $apiEndpoint, $httpClient);
@ -88,7 +88,7 @@ class Mailgun
$clientConfigurator->setApiKey($apiKey); $clientConfigurator->setApiKey($apiKey);
$this->httpClient = $clientConfigurator->createConfiguredClient(); $this->httpClient = $clientConfigurator->createConfiguredClient();
$this->requestFactory = MessageFactoryDiscovery::find(); $this->requestBuilder = $requestBuilder ?: new RequestBuilder();
$this->deserializer = $deserializer ?: new ModelDeserializer(); $this->deserializer = $deserializer ?: new ModelDeserializer();
} }
@ -258,7 +258,7 @@ class Mailgun
*/ */
public function getStatsApi() public function getStatsApi()
{ {
return new Api\Stats($this->httpClient, $this->requestFactory, $this->deserializer); return new Api\Stats($this->httpClient, $this->requestBuilder, $this->deserializer);
} }
/** /**
@ -266,6 +266,6 @@ class Mailgun
*/ */
public function getDomainApi() public function getDomainApi()
{ {
return new Api\Domain($this->httpClient, $this->requestFactory, $this->deserializer); return new Api\Domain($this->httpClient, $this->requestBuilder, $this->deserializer);
} }
} }

View File

@ -0,0 +1,113 @@
<?php
namespace Mailgun;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Message\MultipartStream\MultipartStreamBuilder;
use Http\Message\RequestFactory;
use Psr\Http\Message\RequestInterface;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class RequestBuilder
{
/**
* @var RequestFactory
*/
private $requestFactory;
/**
* @var MultipartStreamBuilder
*/
private $multipartStreamBuilder;
/**
* Creates a new PSR-7 request.
*
* @param string $method
* @param string $uri
* @param array $headers
* @param array|string|null $body Request body. If body is an array we will send a as multipart stream request.
* If array, each array *item* MUST look like:
* array (
* 'content' => string|resource|StreamInterface,
* 'name' => string,
* 'filename'=> string (optional)
* 'headers' => array (optinal) ['header-name' => 'header-value']
* )
*
* @return RequestInterface
*/
public function create($method, $uri, array $headers = [], $body = null)
{
if (!is_array($body)) {
return $this->getRequestFactory()->createRequest($method, $uri, $headers, $body);
}
$builder = $this->getMultipartStreamBuilder();
foreach ($body as $item) {
$name = $item['name'];
$content = $item['content'];
unset($item['name']);
unset($item['content']);
$builder->addResource($name, $content, $item);
}
$multipartStream = $builder->build();
$boundary = $builder->getBoundary();
$headers['Content-Type'] = 'multipart/form-data; boundary='.$boundary;
return $this->getRequestFactory()->createRequest($method, $uri, $headers, $multipartStream);
}
/**
* @return RequestFactory
*/
private function getRequestFactory()
{
if ($this->requestFactory === null) {
$this->requestFactory = MessageFactoryDiscovery::find();
}
return $this->requestFactory;
}
/**
* @param RequestFactory $requestFactory
*
* @return RequestBuilder
*/
public function setRequestFactory($requestFactory)
{
$this->requestFactory = $requestFactory;
return $this;
}
/**
* @return MultipartStreamBuilder
*/
private function getMultipartStreamBuilder()
{
if ($this->multipartStreamBuilder === null) {
$this->multipartStreamBuilder = new MultipartStreamBuilder();
}
return $this->multipartStreamBuilder;
}
/**
* @param MultipartStreamBuilder $multipartStreamBuilder
*
* @return RequestBuilder
*/
public function setMultipartStreamBuilder($multipartStreamBuilder)
{
$this->multipartStreamBuilder = $multipartStreamBuilder;
return $this;
}
}

View File

@ -47,8 +47,8 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
->expects($this->any()) ->expects($this->any())
->method('sendRequest'); ->method('sendRequest');
$requestClient = $this->getMockBuilder('Http\Message\MessageFactory') $requestClient = $this->getMockBuilder('Mailgun\RequestBuilder')
->setMethods(['createRequest', 'createResponse']) ->setMethods(['create'])
->getMock(); ->getMock();
$deserializer = $this->getMockBuilder('Mailgun\Deserializer\ResponseDeserializer') $deserializer = $this->getMockBuilder('Mailgun\Deserializer\ResponseDeserializer')
@ -56,14 +56,7 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
->getMock(); ->getMock();
return $this->getMockBuilder($this->getApiClass()) return $this->getMockBuilder($this->getApiClass())
->setMethods( ->setMethods(['httpGet', 'httpPost', 'httpPostRaw', 'httpDelete', 'httPut'])
[
'httpGet',
'httpPost', 'httpPostRaw', 'postMultipart',
'httpDelete', 'deleteMultipart',
'httPut', 'putMultipart',
]
)
->setConstructorArgs([$httpClient, $requestClient, $deserializer]) ->setConstructorArgs([$httpClient, $requestClient, $deserializer])
->getMock(); ->getMock();
} }

View File

@ -0,0 +1,38 @@
<?php
namespace Mailgun\Tests;
use Mailgun\RequestBuilder;
class RequestBuilderTest extends \PHPUnit_Framework_TestCase
{
public function testCreateSimpleStream()
{
$builder = new RequestBuilder();
$request = $builder->create('GET', 'http://foo.bar', ['Content-Type' => 'application/json'], 'content');
$body = $request->getBody()->__toString();
$contentType = $request->getHeaderLine('Content-Type');
$this->assertContains('content', $body);
$this->assertEquals('application/json', $contentType);
}
public function testCreateMultipartStream()
{
$item0 = ['content' => 'foobar', 'name' => 'username'];
$item1 = ['content' => 'Stockholm', 'name' => 'city'];
$builder = new RequestBuilder();
$request = $builder->create('GET', 'http://foo.bar', ['Content-Type' => 'application/json'], [$item0, $item1]);
$body = $request->getBody()->__toString();
$contentType = $request->getHeaderLine('Content-Type');
$this->assertContains('foobar', $body);
$this->assertContains('username', $body);
$this->assertContains('Stockholm', $body);
$this->assertContains('city', $body);
$this->assertRegExp('|^multipart/form-data; boundary=[0-9a-z]+$|si', $contentType);
}
}