diff --git a/src/Mailgun/Api/Domain.php b/src/Mailgun/Api/Domain.php index 8954ace..c79960d 100644 --- a/src/Mailgun/Api/Domain.php +++ b/src/Mailgun/Api/Domain.php @@ -163,7 +163,7 @@ class Domain extends HttpApi '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); } @@ -188,14 +188,7 @@ class Domain extends HttpApi 'password' => $pass, ]; - $response = $this->httpPutMultipart( - sprintf( - '/v3/domains/%s/credentials/%s', - $domain, - $login - ), - $params - ); + $response = $this->httpPut(sprintf('/v3/domains/%s/credentials/%s', $domain, $login), $params); return $this->deserializer->deserialize($response, UpdateCredentialResponse::class); } diff --git a/src/Mailgun/Api/HttpApi.php b/src/Mailgun/Api/HttpApi.php index 0aedb33..fe45102 100644 --- a/src/Mailgun/Api/HttpApi.php +++ b/src/Mailgun/Api/HttpApi.php @@ -2,16 +2,11 @@ namespace Mailgun\Api; -use Http\Client\Common\HttpMethodsClient; use Http\Client\Exception as HttplugException; 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\Exception\HttpServerException; +use Mailgun\RequestBuilder; use Mailgun\Resource\Api\ErrorResponse; use Psr\Http\Message\ResponseInterface; @@ -23,7 +18,7 @@ abstract class HttpApi /** * The HTTP client. * - * @var HttpMethodsClient + * @var HttpClient */ private $httpClient; @@ -33,13 +28,19 @@ abstract class HttpApi protected $serializer; /** - * @param HttpClient $httpClient - * @param RequestFactory $requestFactory - * @param ResponseDeserializer $serializer + * @var RequestBuilder */ - 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; } @@ -78,7 +79,9 @@ abstract class HttpApi } try { - $response = $this->httpClient->get($path, $requestHeaders); + $response = $this->httpClient->sendRequest( + $this->requestBuilder->create('GET', $path, $requestHeaders) + ); } catch (HttplugException\NetworkException $e) { throw HttpServerException::networkError($e); } @@ -100,33 +103,21 @@ abstract class HttpApi 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. * - * @param string $path Request path. - * @param string $body Request body. - * @param array $requestHeaders Request headers. + * @param string $path Request path. + * @param array|string $body Request body. + * @param array $requestHeaders Request headers. * * @return ResponseInterface */ protected function httpPostRaw($path, $body, array $requestHeaders = []) { try { - $response = $this->httpClient->post($path, $requestHeaders, $body); + $response = $this->httpClient->sendRequest( + $this->requestBuilder->create('POST', $path, $requestHeaders, $body) + ); } catch (HttplugException\NetworkException $e) { throw HttpServerException::networkError($e); } @@ -146,7 +137,9 @@ abstract class HttpApi protected function httpPut($path, array $parameters = [], array $requestHeaders = []) { 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) { throw HttpServerException::networkError($e); } @@ -154,20 +147,6 @@ abstract class HttpApi 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. * @@ -180,7 +159,9 @@ abstract class HttpApi protected function httpDelete($path, array $parameters = [], array $requestHeaders = []) { 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) { throw HttpServerException::networkError($e); } @@ -188,60 +169,6 @@ abstract class HttpApi 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. * diff --git a/src/Mailgun/Mailgun.php b/src/Mailgun/Mailgun.php index 22732da..08f8dc9 100644 --- a/src/Mailgun/Mailgun.php +++ b/src/Mailgun/Mailgun.php @@ -11,8 +11,6 @@ namespace Mailgun; use Http\Client\Common\HttpMethodsClient; use Http\Client\HttpClient; -use Http\Discovery\MessageFactoryDiscovery; -use Http\Message\RequestFactory; use Mailgun\Connection\RestClient; use Mailgun\Constants\ExceptionMessages; use Mailgun\Lists\OptInHandler; @@ -50,9 +48,9 @@ class Mailgun private $deserializer; /** - * @var RequestFactory + * @var RequestBuilder */ - private $requestFactory; + private $requestBuilder; /** * @param string|null $apiKey @@ -60,13 +58,15 @@ class Mailgun * @param string $apiEndpoint * @param ResponseDeserializer|null $deserializer * @param HttpClientConfigurator|null $clientConfigurator + * @param RequestBuilder|null $requestBuilder */ public function __construct( $apiKey = null, HttpClient $httpClient = null, /* Deprecated, will be removed in 3.0 */ $apiEndpoint = 'api.mailgun.net', /* Deprecated, will be removed in 3.0 */ ResponseDeserializer $deserializer = null, - HttpClientConfigurator $clientConfigurator = null + HttpClientConfigurator $clientConfigurator = null, + RequestBuilder $requestBuilder = null ) { $this->apiKey = $apiKey; $this->restClient = new RestClient($apiKey, $apiEndpoint, $httpClient); @@ -88,7 +88,7 @@ class Mailgun $clientConfigurator->setApiKey($apiKey); $this->httpClient = $clientConfigurator->createConfiguredClient(); - $this->requestFactory = MessageFactoryDiscovery::find(); + $this->requestBuilder = $requestBuilder ?: new RequestBuilder(); $this->deserializer = $deserializer ?: new ModelDeserializer(); } @@ -258,7 +258,7 @@ class Mailgun */ 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() { - return new Api\Domain($this->httpClient, $this->requestFactory, $this->deserializer); + return new Api\Domain($this->httpClient, $this->requestBuilder, $this->deserializer); } } diff --git a/src/Mailgun/RequestBuilder.php b/src/Mailgun/RequestBuilder.php new file mode 100644 index 0000000..23bdbc7 --- /dev/null +++ b/src/Mailgun/RequestBuilder.php @@ -0,0 +1,113 @@ + + */ +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; + } +} diff --git a/tests/Api/TestCase.php b/tests/Api/TestCase.php index 2f380a3..5d2399a 100644 --- a/tests/Api/TestCase.php +++ b/tests/Api/TestCase.php @@ -47,8 +47,8 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase ->expects($this->any()) ->method('sendRequest'); - $requestClient = $this->getMockBuilder('Http\Message\MessageFactory') - ->setMethods(['createRequest', 'createResponse']) + $requestClient = $this->getMockBuilder('Mailgun\RequestBuilder') + ->setMethods(['create']) ->getMock(); $deserializer = $this->getMockBuilder('Mailgun\Deserializer\ResponseDeserializer') @@ -56,14 +56,7 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase ->getMock(); return $this->getMockBuilder($this->getApiClass()) - ->setMethods( - [ - 'httpGet', - 'httpPost', 'httpPostRaw', 'postMultipart', - 'httpDelete', 'deleteMultipart', - 'httPut', 'putMultipart', - ] - ) + ->setMethods(['httpGet', 'httpPost', 'httpPostRaw', 'httpDelete', 'httPut']) ->setConstructorArgs([$httpClient, $requestClient, $deserializer]) ->getMock(); } diff --git a/tests/RequestBuilderTest.php b/tests/RequestBuilderTest.php new file mode 100644 index 0000000..e91fdc8 --- /dev/null +++ b/tests/RequestBuilderTest.php @@ -0,0 +1,38 @@ +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); + } +}