From 738e6e32e27c694b919903025b919a3aa781ba03 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Mon, 24 Oct 2016 19:01:32 +0200 Subject: [PATCH] POC - Better api (#192) * Added base for the new API * code style * Added response classes * Added support for serializer * The abstract API should not know of Mailgun * Minor * minor * Using a client configrator * code style * Put HTTPClient in the configurator * Do not use the api() function * Use stable version of Assert * style * Fixed tests * make the httpClient private * Renamed ResponseSerializer to ResponseDeserializer * Disabled tests that are testing error messages with Assert * style fixes * Refactoring fix --- composer.json | 4 +- src/Mailgun/Api/AbstractApi.php | 150 ++++++++++++++++++ src/Mailgun/Api/Stats.php | 45 ++++++ src/Mailgun/Assert.php | 18 +++ .../Exceptions/GenericHTTPError.php | 4 +- .../Exceptions/InvalidCredentials.php | 4 +- .../Connection/Exceptions/MissingEndpoint.php | 4 +- .../Exceptions/MissingRequiredParameters.php | 4 +- .../Exceptions/NoDomainsConfigured.php | 4 +- src/Mailgun/Exception.php | 12 ++ src/Mailgun/Exception/HttpClientException.php | 31 ++++ src/Mailgun/Exception/HttpServerException.php | 26 +++ .../Exception/InvalidArgumentException.php | 12 ++ src/Mailgun/Exception/SerializeException.php | 9 ++ src/Mailgun/HttpClientConfigurator.php | 142 +++++++++++++++++ src/Mailgun/Mailgun.php | 67 +++++++- .../Messages/Exceptions/InvalidParameter.php | 4 +- .../Exceptions/InvalidParameterType.php | 4 +- .../MissingRequiredMIMEParameters.php | 4 +- .../Messages/Exceptions/TooManyParameters.php | 4 +- .../Resource/Api/Stats/AllResponse.php | 62 ++++++++ src/Mailgun/Resource/Api/Stats/Item.php | 87 ++++++++++ .../Resource/Api/Stats/TotalResponse.php | 92 +++++++++++ src/Mailgun/Resource/Api/Stats/TotalStats.php | 75 +++++++++ src/Mailgun/Resource/CreatableFromArray.php | 13 ++ src/Mailgun/Serializer/ArraySerializer.php | 35 ++++ src/Mailgun/Serializer/ObjectSerializer.php | 42 +++++ src/Mailgun/Serializer/PSR7Serializer.php | 24 +++ .../Serializer/ResponseDeserializer.php | 19 +++ tests/Api/StatsTest.php | 64 ++++++++ tests/Api/TestCase.php | 35 ++++ 31 files changed, 1082 insertions(+), 18 deletions(-) create mode 100644 src/Mailgun/Api/AbstractApi.php create mode 100644 src/Mailgun/Api/Stats.php create mode 100644 src/Mailgun/Assert.php create mode 100644 src/Mailgun/Exception.php create mode 100644 src/Mailgun/Exception/HttpClientException.php create mode 100644 src/Mailgun/Exception/HttpServerException.php create mode 100644 src/Mailgun/Exception/InvalidArgumentException.php create mode 100644 src/Mailgun/Exception/SerializeException.php create mode 100644 src/Mailgun/HttpClientConfigurator.php create mode 100644 src/Mailgun/Resource/Api/Stats/AllResponse.php create mode 100644 src/Mailgun/Resource/Api/Stats/Item.php create mode 100644 src/Mailgun/Resource/Api/Stats/TotalResponse.php create mode 100644 src/Mailgun/Resource/Api/Stats/TotalStats.php create mode 100644 src/Mailgun/Resource/CreatableFromArray.php create mode 100644 src/Mailgun/Serializer/ArraySerializer.php create mode 100644 src/Mailgun/Serializer/ObjectSerializer.php create mode 100644 src/Mailgun/Serializer/PSR7Serializer.php create mode 100644 src/Mailgun/Serializer/ResponseDeserializer.php create mode 100644 tests/Api/StatsTest.php create mode 100644 tests/Api/TestCase.php diff --git a/composer.json b/composer.json index 0c68194..0381a24 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,9 @@ "php-http/httplug": "^1.0", "php-http/multipart-stream-builder": "^0.1", "php-http/message": "^1.0", - "php-http/discovery": "^1.0" + "php-http/client-common": "^1.0", + "php-http/discovery": "^1.0", + "webmozart/assert": "^1.1" }, "require-dev": { "phpunit/phpunit": "~4.6", diff --git a/src/Mailgun/Api/AbstractApi.php b/src/Mailgun/Api/AbstractApi.php new file mode 100644 index 0000000..ec4b08e --- /dev/null +++ b/src/Mailgun/Api/AbstractApi.php @@ -0,0 +1,150 @@ + + */ +abstract class AbstractApi +{ + /** + * The HTTP client. + * + * @var HttpMethodsClient + */ + private $httpClient; + + /** + * @var ResponseDeserializer + */ + protected $serializer; + + /** + * @param HttpClient $httpClient + * @param RequestFactory $requestFactory + * @param ResponseDeserializer $serializer + */ + public function __construct(HttpClient $httpClient, RequestFactory $requestFactory, ResponseDeserializer $serializer) + { + $this->httpClient = new HttpMethodsClient($httpClient, $requestFactory); + $this->serializer = $serializer; + } + + /** + * Send a GET request with query parameters. + * + * @param string $path Request path. + * @param array $parameters GET parameters. + * @param array $requestHeaders Request Headers. + * + * @return ResponseInterface + */ + protected function get($path, array $parameters = [], array $requestHeaders = []) + { + if (count($parameters) > 0) { + $path .= '?'.http_build_query($parameters); + } + + try { + $response = $this->httpClient->get($path, $requestHeaders); + } catch (HttplugException\NetworkException $e) { + throw HttpServerException::networkError($e); + } + + return $response; + } + + /** + * Send a POST request with JSON-encoded parameters. + * + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. + * + * @return ResponseInterface + */ + protected function post($path, array $parameters = [], array $requestHeaders = []) + { + return $this->postRaw($path, $this->createJsonBody($parameters), $requestHeaders); + } + + /** + * Send a POST request with raw data. + * + * @param string $path Request path. + * @param string $body Request body. + * @param array $requestHeaders Request headers. + * + * @return ResponseInterface + */ + protected function postRaw($path, $body, array $requestHeaders = []) + { + try { + $response = $this->httpClient->post($path, $requestHeaders, $body); + } catch (HttplugException\NetworkException $e) { + throw HttpServerException::networkError($e); + } + + return $response; + } + + /** + * Send a PUT request with JSON-encoded parameters. + * + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. + * + * @return ResponseInterface + */ + protected function put($path, array $parameters = [], array $requestHeaders = []) + { + try { + $response = $this->httpClient->put($path, $requestHeaders, $this->createJsonBody($parameters)); + } catch (HttplugException\NetworkException $e) { + throw HttpServerException::networkError($e); + } + + return $response; + } + + /** + * Send a DELETE request with JSON-encoded parameters. + * + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. + * + * @return ResponseInterface + */ + protected function delete($path, array $parameters = [], array $requestHeaders = []) + { + try { + $response = $this->httpClient->delete($path, $requestHeaders, $this->createJsonBody($parameters)); + } catch (HttplugException\NetworkException $e) { + throw HttpServerException::networkError($e); + } + + return $response; + } + + /** + * Create a JSON encoded version of an array of parameters. + * + * @param array $parameters Request parameters + * + * @return null|string + */ + protected function createJsonBody(array $parameters) + { + return (count($parameters) === 0) ? null : json_encode($parameters, empty($parameters) ? JSON_FORCE_OBJECT : 0); + } +} diff --git a/src/Mailgun/Api/Stats.php b/src/Mailgun/Api/Stats.php new file mode 100644 index 0000000..8ec7c8d --- /dev/null +++ b/src/Mailgun/Api/Stats.php @@ -0,0 +1,45 @@ + + */ +class Stats extends AbstractApi +{ + /** + * @param string $domain + * @param array $params + * + * @return TotalResponse|array + */ + public function total($domain, array $params = []) + { + Assert::stringNotEmpty($domain); + + $response = $this->get(sprintf('/v3/%s/stats/total', rawurlencode($domain)), $params); + + return $this->serializer->deserialize($response, TotalResponse::class); + } + + /** + * @param $domain + * @param array $params + * + * @return AllResponse|array + */ + public function all($domain, array $params = []) + { + Assert::stringNotEmpty($domain); + + $response = $this->get(sprintf('/v3/%s/stats', rawurlencode($domain)), $params); + + return $this->serializer->deserialize($response, AllResponse::class); + } +} diff --git a/src/Mailgun/Assert.php b/src/Mailgun/Assert.php new file mode 100644 index 0000000..cb98d3e --- /dev/null +++ b/src/Mailgun/Assert.php @@ -0,0 +1,18 @@ + + */ +class Assert extends \Webmozart\Assert\Assert +{ + protected static function createInvalidArgumentException($message) + { + return new InvalidArgumentException($message); + } +} diff --git a/src/Mailgun/Connection/Exceptions/GenericHTTPError.php b/src/Mailgun/Connection/Exceptions/GenericHTTPError.php index 870aedc..c4e5dd0 100644 --- a/src/Mailgun/Connection/Exceptions/GenericHTTPError.php +++ b/src/Mailgun/Connection/Exceptions/GenericHTTPError.php @@ -9,7 +9,9 @@ namespace Mailgun\Connection\Exceptions; -class GenericHTTPError extends \Exception +use Mailgun\Exception; + +class GenericHTTPError extends \Exception implements Exception { protected $httpResponseCode; protected $httpResponseBody; diff --git a/src/Mailgun/Connection/Exceptions/InvalidCredentials.php b/src/Mailgun/Connection/Exceptions/InvalidCredentials.php index a647cd0..cf9280d 100644 --- a/src/Mailgun/Connection/Exceptions/InvalidCredentials.php +++ b/src/Mailgun/Connection/Exceptions/InvalidCredentials.php @@ -9,6 +9,8 @@ namespace Mailgun\Connection\Exceptions; -class InvalidCredentials extends \Exception +use Mailgun\Exception; + +class InvalidCredentials extends \Exception implements Exception { } diff --git a/src/Mailgun/Connection/Exceptions/MissingEndpoint.php b/src/Mailgun/Connection/Exceptions/MissingEndpoint.php index 3fe20e2..fcaba4b 100644 --- a/src/Mailgun/Connection/Exceptions/MissingEndpoint.php +++ b/src/Mailgun/Connection/Exceptions/MissingEndpoint.php @@ -9,6 +9,8 @@ namespace Mailgun\Connection\Exceptions; -class MissingEndpoint extends \Exception +use Mailgun\Exception; + +class MissingEndpoint extends \Exception implements Exception { } diff --git a/src/Mailgun/Connection/Exceptions/MissingRequiredParameters.php b/src/Mailgun/Connection/Exceptions/MissingRequiredParameters.php index c0b590e..2873607 100644 --- a/src/Mailgun/Connection/Exceptions/MissingRequiredParameters.php +++ b/src/Mailgun/Connection/Exceptions/MissingRequiredParameters.php @@ -9,6 +9,8 @@ namespace Mailgun\Connection\Exceptions; -class MissingRequiredParameters extends \Exception +use Mailgun\Exception; + +class MissingRequiredParameters extends \Exception implements Exception { } diff --git a/src/Mailgun/Connection/Exceptions/NoDomainsConfigured.php b/src/Mailgun/Connection/Exceptions/NoDomainsConfigured.php index 2bb2308..7a5d9dd 100644 --- a/src/Mailgun/Connection/Exceptions/NoDomainsConfigured.php +++ b/src/Mailgun/Connection/Exceptions/NoDomainsConfigured.php @@ -9,6 +9,8 @@ namespace Mailgun\Connection\Exceptions; -class NoDomainsConfigured extends \Exception +use Mailgun\Exception; + +class NoDomainsConfigured extends \Exception implements Exception { } diff --git a/src/Mailgun/Exception.php b/src/Mailgun/Exception.php new file mode 100644 index 0000000..ea6fdf3 --- /dev/null +++ b/src/Mailgun/Exception.php @@ -0,0 +1,12 @@ + + */ +interface Exception +{ +} diff --git a/src/Mailgun/Exception/HttpClientException.php b/src/Mailgun/Exception/HttpClientException.php new file mode 100644 index 0000000..df07a34 --- /dev/null +++ b/src/Mailgun/Exception/HttpClientException.php @@ -0,0 +1,31 @@ + + */ +class HttpClientException extends \RuntimeException implements Exception +{ + public static function badRequest() + { + return new self('The parameters passed to the API were invalid. Check your inputs!', 400); + } + + public static function unauthorized() + { + return new self('Your credentials are incorrect.', 401); + } + + public static function requestFailed() + { + return new self('Parameters were valid but request failed. Try again.', 402); + } + + public static function notFound() + { + return new self('The endpoint you tried to access does not exist. Check your URL.', 404); + } +} diff --git a/src/Mailgun/Exception/HttpServerException.php b/src/Mailgun/Exception/HttpServerException.php new file mode 100644 index 0000000..1c123f3 --- /dev/null +++ b/src/Mailgun/Exception/HttpServerException.php @@ -0,0 +1,26 @@ + + */ +class HttpServerException extends \RuntimeException implements Exception +{ + public static function serverError($httpStatus = 500) + { + return new self('An unexpected error occurred at Mailgun\'s servers. Try again later and contact support of the error sill exists.', $httpStatus); + } + + public static function networkError(\Exception $previous) + { + return new self('Mailgun\'s servers was unreachable.', 0, $previous); + } + + public static function unknownHttpResponseCode($code) + { + return new self(sprintf('Unknown HTTP response code ("%d") received from the API server', $code)); + } +} diff --git a/src/Mailgun/Exception/InvalidArgumentException.php b/src/Mailgun/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..8fbcee6 --- /dev/null +++ b/src/Mailgun/Exception/InvalidArgumentException.php @@ -0,0 +1,12 @@ + + */ +class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/src/Mailgun/Exception/SerializeException.php b/src/Mailgun/Exception/SerializeException.php new file mode 100644 index 0000000..da1e200 --- /dev/null +++ b/src/Mailgun/Exception/SerializeException.php @@ -0,0 +1,9 @@ + + */ +class HttpClientConfigurator +{ + /** + * @var string + */ + private $endpoint = 'https://api.mailgun.net'; + + /** + * @var string + */ + private $apiKey; + + /** + * @var UriFactory + */ + private $uriFactory; + + /** + * @var HttpClient + */ + private $httpClient; + + /** + * @return PluginClient + */ + public function createConfiguredClient() + { + $plugins = [ + new Plugin\AddHostPlugin($this->getUriFactory()->createUri($this->getEndpoint())), + new Plugin\HeaderDefaultsPlugin([ + 'User-Agent' => 'mailgun-sdk-php/v2 (https://github.com/mailgun/mailgun-php)', + 'Authorization' => 'Basic '.base64_encode(sprintf('api:%s', $this->getApiKey())), + ]), + ]; + + return new PluginClient($this->getHttpClient(), $plugins); + } + + /** + * @return string + */ + private function getEndpoint() + { + return $this->endpoint; + } + + /** + * @param string $endpoint + * + * @return HttpClientConfigurator + */ + public function setEndpoint($endpoint) + { + $this->endpoint = $endpoint; + + return $this; + } + + /** + * @return string + */ + private function getApiKey() + { + return $this->apiKey; + } + + /** + * @param string $apiKey + * + * @return HttpClientConfigurator + */ + public function setApiKey($apiKey) + { + $this->apiKey = $apiKey; + + return $this; + } + + /** + * @return UriFactory + */ + private function getUriFactory() + { + if ($this->uriFactory === null) { + $this->uriFactory = UriFactoryDiscovery::find(); + } + + return $this->uriFactory; + } + + /** + * @param UriFactory $uriFactory + * + * @return HttpClientConfigurator + */ + public function setUriFactory(UriFactory $uriFactory) + { + $this->uriFactory = $uriFactory; + + return $this; + } + + /** + * @return HttpClient + */ + private function getHttpClient() + { + if ($this->httpClient === null) { + $this->httpClient = HttpClientDiscovery::find(); + } + + return $this->httpClient; + } + + /** + * @param HttpClient $httpClient + * + * @return HttpClientConfigurator + */ + public function setHttpClient(HttpClient $httpClient) + { + $this->httpClient = $httpClient; + + return $this; + } +} diff --git a/src/Mailgun/Mailgun.php b/src/Mailgun/Mailgun.php index 557752a..2d0071a 100644 --- a/src/Mailgun/Mailgun.php +++ b/src/Mailgun/Mailgun.php @@ -9,24 +9,28 @@ 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; use Mailgun\Messages\BatchMessage; use Mailgun\Messages\Exceptions; use Mailgun\Messages\MessageBuilder; +use Mailgun\Serializer\ObjectSerializer; +use Mailgun\Serializer\ResponseDeserializer; /** * This class is the base class for the Mailgun SDK. - * See the official documentation (link below) for usage instructions. - * - * @link https://github.com/mailgun/mailgun-php/blob/master/README.md */ class Mailgun { /** * @var RestClient + * + * @depracated Will be removed in 3.0 */ protected $restClient; @@ -36,17 +40,56 @@ class Mailgun protected $apiKey; /** - * @param string|null $apiKey - * @param HttpClient $httpClient - * @param string $apiEndpoint + * @var HttpMethodsClient + */ + private $httpClient; + + /** + * @var ResponseDeserializer + */ + private $serializer; + + /** + * @var RequestFactory + */ + private $requestFactory; + + /** + * @param string|null $apiKey + * @param HttpClient|null $httpClient + * @param string $apiEndpoint + * @param ResponseDeserializer|null $serializer + * @param HttpClientConfigurator|null $clientConfigurator */ public function __construct( $apiKey = null, - HttpClient $httpClient = null, - $apiEndpoint = 'api.mailgun.net' + HttpClient $httpClient = null, /* Deprecated, will be removed in 3.0 */ + $apiEndpoint = 'api.mailgun.net', /* Deprecated, will be removed in 3.0 */ + ResponseDeserializer $serializer = null, + HttpClientConfigurator $clientConfigurator = null ) { $this->apiKey = $apiKey; $this->restClient = new RestClient($apiKey, $apiEndpoint, $httpClient); + + if (null === $clientConfigurator) { + $clientConfigurator = new HttpClientConfigurator(); + + /* + * To be backward compatible + */ + if ($apiEndpoint !== 'api.mailgun.net') { + $clientConfigurator->setEndpoint($apiEndpoint); + } + if ($httpClient !== null) { + $clientConfigurator->setHttpClient($httpClient); + } + } + + $clientConfigurator->setApiKey($apiKey); + + $this->httpClient = $clientConfigurator->createConfiguredClient(); + $this->requestFactory = MessageFactoryDiscovery::find(); + $this->serializer = $serializer ?: new ObjectSerializer(); } /** @@ -209,4 +252,12 @@ class Mailgun { return new BatchMessage($this->restClient, $workingDomain, $autoSend); } + + /** + * @return Api\Stats + */ + public function getStatsApi() + { + return new Api\Stats($this->httpClient, $this->requestFactory, $this->serializer); + } } diff --git a/src/Mailgun/Messages/Exceptions/InvalidParameter.php b/src/Mailgun/Messages/Exceptions/InvalidParameter.php index 18c9ea6..6ea2681 100644 --- a/src/Mailgun/Messages/Exceptions/InvalidParameter.php +++ b/src/Mailgun/Messages/Exceptions/InvalidParameter.php @@ -9,6 +9,8 @@ namespace Mailgun\Messages\Exceptions; -class InvalidParameter extends \Exception +use Mailgun\Exception; + +class InvalidParameter extends \Exception implements Exception { } diff --git a/src/Mailgun/Messages/Exceptions/InvalidParameterType.php b/src/Mailgun/Messages/Exceptions/InvalidParameterType.php index c19bae4..4751744 100644 --- a/src/Mailgun/Messages/Exceptions/InvalidParameterType.php +++ b/src/Mailgun/Messages/Exceptions/InvalidParameterType.php @@ -9,6 +9,8 @@ namespace Mailgun\Messages\Exceptions; -class InvalidParameterType extends \Exception +use Mailgun\Exception; + +class InvalidParameterType extends \Exception implements Exception { } diff --git a/src/Mailgun/Messages/Exceptions/MissingRequiredMIMEParameters.php b/src/Mailgun/Messages/Exceptions/MissingRequiredMIMEParameters.php index aeb474d..ece0103 100644 --- a/src/Mailgun/Messages/Exceptions/MissingRequiredMIMEParameters.php +++ b/src/Mailgun/Messages/Exceptions/MissingRequiredMIMEParameters.php @@ -9,6 +9,8 @@ namespace Mailgun\Messages\Exceptions; -class MissingRequiredMIMEParameters extends \Exception +use Mailgun\Exception; + +class MissingRequiredMIMEParameters extends \Exception implements Exception { } diff --git a/src/Mailgun/Messages/Exceptions/TooManyParameters.php b/src/Mailgun/Messages/Exceptions/TooManyParameters.php index f5401f2..901d093 100644 --- a/src/Mailgun/Messages/Exceptions/TooManyParameters.php +++ b/src/Mailgun/Messages/Exceptions/TooManyParameters.php @@ -9,6 +9,8 @@ namespace Mailgun\Messages\Exceptions; -class TooManyParameters extends \Exception +use Mailgun\Exception; + +class TooManyParameters extends \Exception implements Exception { } diff --git a/src/Mailgun/Resource/Api/Stats/AllResponse.php b/src/Mailgun/Resource/Api/Stats/AllResponse.php new file mode 100644 index 0000000..d6dcd42 --- /dev/null +++ b/src/Mailgun/Resource/Api/Stats/AllResponse.php @@ -0,0 +1,62 @@ + + */ +class AllResponse implements CreatableFromArray +{ + /** + * @var int + */ + private $totalCount; + + /** + * @var Item[] + */ + private $items; + + /** + * @param int $totalCount + * @param Item[] $items + */ + public function __construct($totalCount, array $items) + { + $this->totalCount = $totalCount; + $this->items = $items; + } + + /** + * @param array $data + * + * @return AllResponse + */ + public static function createFromArray(array $data) + { + $items = []; + foreach ($data['items'] as $i) { + $items[] = new Item($i['id'], $i['event'], $i['total_count'], $i['tags'], new \DateTime($i['created_at'])); + } + + return new self($data['total_count'], $items); + } + + /** + * @return int + */ + public function getTotalCount() + { + return $this->totalCount; + } + + /** + * @return Item[] + */ + public function getItems() + { + return $this->items; + } +} diff --git a/src/Mailgun/Resource/Api/Stats/Item.php b/src/Mailgun/Resource/Api/Stats/Item.php new file mode 100644 index 0000000..f20ed39 --- /dev/null +++ b/src/Mailgun/Resource/Api/Stats/Item.php @@ -0,0 +1,87 @@ +id = $id; + $this->event = $event; + $this->totalCount = $totalCount; + $this->tags = $tags; + $this->createdAt = $createdAt; + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getEvent() + { + return $this->event; + } + + /** + * @return string + */ + public function getTotalCount() + { + return $this->totalCount; + } + + /** + * @return \string[] + */ + public function getTags() + { + return $this->tags; + } + + /** + * @return \DateTime + */ + public function getCreatedAt() + { + return $this->createdAt; + } +} diff --git a/src/Mailgun/Resource/Api/Stats/TotalResponse.php b/src/Mailgun/Resource/Api/Stats/TotalResponse.php new file mode 100644 index 0000000..1bfc1e8 --- /dev/null +++ b/src/Mailgun/Resource/Api/Stats/TotalResponse.php @@ -0,0 +1,92 @@ + + */ +class TotalResponse implements CreatableFromArray +{ + /** + * @var \DateTime + */ + private $start; + + /** + * @var \DateTime + */ + private $end; + + /** + * @var string + */ + private $resolution; + + /** + * @var TotalStats[] + */ + private $stats; + + /** + * @param \DateTime $start + * @param \DateTime $end + * @param string $resolution + * @param TotalStats[] $stats + */ + public function __construct(\DateTime $start, \DateTime $end, $resolution, array $stats) + { + $this->start = $start; + $this->end = $end; + $this->resolution = $resolution; + $this->stats = $stats; + } + + /** + * @param array $data + * + * @return TotalResponse + */ + public static function createFromArray(array $data) + { + $stats = []; + foreach ($data['stats'] as $s) { + $stats[] = new TotalStats(new \DateTime($s['time']), $s['accepted'], $s['delivered'], $s['failed']); + } + + return new self(new \DateTime($data['start']), new \DateTime($data['end']), $data['resolution'], $stats); + } + + /** + * @return \DateTime + */ + public function getStart() + { + return $this->start; + } + + /** + * @return \DateTime + */ + public function getEnd() + { + return $this->end; + } + + /** + * @return string + */ + public function getResolution() + { + return $this->resolution; + } + + /** + * @return TotalStats[] + */ + public function getStats() + { + return $this->stats; + } +} diff --git a/src/Mailgun/Resource/Api/Stats/TotalStats.php b/src/Mailgun/Resource/Api/Stats/TotalStats.php new file mode 100644 index 0000000..f04d714 --- /dev/null +++ b/src/Mailgun/Resource/Api/Stats/TotalStats.php @@ -0,0 +1,75 @@ + + */ +class TotalStats +{ + /** + * @var \DateTime + */ + private $time; + + /** + * @var array + */ + private $accepted; + + /** + * @var array + */ + private $delivered; + + /** + * @var array + */ + private $failed; + + /** + * @param \DateTime $time + * @param array $accepted + * @param array $delivered + * @param array $failed + */ + public function __construct(\DateTime $time, array $accepted, array $delivered, array $failed) + { + $this->time = $time; + $this->accepted = $accepted; + $this->delivered = $delivered; + $this->failed = $failed; + } + + /** + * @return \DateTime + */ + public function getTime() + { + return $this->time; + } + + /** + * @return array + */ + public function getAccepted() + { + return $this->accepted; + } + + /** + * @return array + */ + public function getDelivered() + { + return $this->delivered; + } + + /** + * @return array + */ + public function getFailed() + { + return $this->failed; + } +} diff --git a/src/Mailgun/Resource/CreatableFromArray.php b/src/Mailgun/Resource/CreatableFromArray.php new file mode 100644 index 0000000..1ef9e57 --- /dev/null +++ b/src/Mailgun/Resource/CreatableFromArray.php @@ -0,0 +1,13 @@ + + */ +class ArraySerializer implements ResponseDeserializer +{ + /** + * @param ResponseInterface $response + * @param string $class + * + * @return array + */ + public function deserialize(ResponseInterface $response, $class) + { + $body = $response->getBody()->__toString(); + if (strpos($response->getHeaderLine('Content-Type'), 'application/json') !== 0) { + throw new SerializeException('The ArraySerializer cannot deserialize response with Content-Type:'.$response->getHeaderLine('Content-Type')); + } + + $content = json_decode($body, true); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new SerializeException(sprintf('Error (%d) when trying to json_decode response', json_last_error())); + } + + return $content; + } +} diff --git a/src/Mailgun/Serializer/ObjectSerializer.php b/src/Mailgun/Serializer/ObjectSerializer.php new file mode 100644 index 0000000..6e1b312 --- /dev/null +++ b/src/Mailgun/Serializer/ObjectSerializer.php @@ -0,0 +1,42 @@ + + */ +class ObjectSerializer implements ResponseDeserializer +{ + /** + * @param ResponseInterface $response + * @param string $class + * + * @return ResponseInterface + */ + public function deserialize(ResponseInterface $response, $class) + { + $body = $response->getBody()->__toString(); + if (strpos($response->getHeaderLine('Content-Type'), 'application/json') !== 0) { + throw new SerializeException('The ObjectSerializer cannot deserialize response with Content-Type:'.$response->getHeaderLine('Content-Type')); + } + + $data = json_decode($body, true); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new SerializeException(sprintf('Error (%d) when trying to json_decode response', json_last_error())); + } + + if (is_subclass_of($class, CreatableFromArray::class)) { + $object = call_user_func($class.'::createFromArray', $data); + } else { + $object = new $class($data); + } + + return $object; + } +} diff --git a/src/Mailgun/Serializer/PSR7Serializer.php b/src/Mailgun/Serializer/PSR7Serializer.php new file mode 100644 index 0000000..1044750 --- /dev/null +++ b/src/Mailgun/Serializer/PSR7Serializer.php @@ -0,0 +1,24 @@ + + */ +class PSR7Serializer implements ResponseDeserializer +{ + /** + * @param ResponseInterface $response + * @param string $class + * + * @return ResponseInterface + */ + public function deserialize(ResponseInterface $response, $class) + { + return $response; + } +} diff --git a/src/Mailgun/Serializer/ResponseDeserializer.php b/src/Mailgun/Serializer/ResponseDeserializer.php new file mode 100644 index 0000000..a013d7d --- /dev/null +++ b/src/Mailgun/Serializer/ResponseDeserializer.php @@ -0,0 +1,19 @@ + + */ +class StatsTest extends TestCase +{ + protected function getApiClass() + { + return 'Mailgun\Api\Stats'; + } + + public function testTotal() + { + $data = [ + 'foo' => 'bar', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('/v3/domain/stats/total', $data) + ->willReturn(new Response()); + + $api->total('domain', $data); + } + + /** + * expectedException \Mailgun\Exception\InvalidArgumentException. + */ + //public function testTotalInvalidArgument() + //{ + // $api = $this->getApiMock(); + // $api->total(''); + //} + + public function testAll() + { + $data = [ + 'foo' => 'bar', + ]; + + $api = $this->getApiMock(); + $api->expects($this->once()) + ->method('get') + ->with('/v3/domain/stats', $data) + ->willReturn(new Response()); + + $api->all('domain', $data); + } + + /* + * expectedException \Mailgun\Exception\InvalidArgumentException + */ + //public function testAllInvalidArgument() + //{ + // $api = $this->getApiMock(); + // $api->all(''); + //} +} diff --git a/tests/Api/TestCase.php b/tests/Api/TestCase.php new file mode 100644 index 0000000..0ac2e93 --- /dev/null +++ b/tests/Api/TestCase.php @@ -0,0 +1,35 @@ + + * @author Contributors of https://github.com/KnpLabs/php-github-api + */ +abstract class TestCase extends \PHPUnit_Framework_TestCase +{ + abstract protected function getApiClass(); + + protected function getApiMock() + { + $httpClient = $this->getMockBuilder('Http\Client\HttpClient') + ->setMethods(['sendRequest']) + ->getMock(); + $httpClient + ->expects($this->any()) + ->method('sendRequest'); + + $requestClient = $this->getMockBuilder('Http\Message\MessageFactory') + ->setMethods(['createRequest', 'createResponse']) + ->getMock(); + + $serializer = $this->getMockBuilder('Mailgun\Serializer\ResponseDeserializer') + ->setMethods(['deserialize']) + ->getMock(); + + return $this->getMockBuilder($this->getApiClass()) + ->setMethods(['get', 'post', 'postRaw', 'delete', 'put']) + ->setConstructorArgs([$httpClient, $requestClient, $serializer]) + ->getMock(); + } +}