diff --git a/.travis.yml b/.travis.yml index 79178e0..fadbf8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - 5.4 - 5.5 - 5.6 - 7.0 diff --git a/README.md b/README.md index 5468156..51df3bb 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,27 @@ curl -sS https://getcomposer.org/installer | php # Add Mailgun as a dependency php composer.phar require mailgun/mailgun-php:~1.7.2 -``` +``` + +You do also need to choose what library to use when you are sending http messages. Consult the +[php-http/client-implementation](https://packagist.org/providers/php-http/client-implementation) virtual package to +find adapters to use. For more information about virtual packages please refer to +[Httplug](http://docs.httplug.io/en/latest/virtual-package/). Example: + +```bash +php composer.phar require php-http/guzzle6-adapter:^1.0 +``` + +When creating a new `Mailgun` object you must provide an instance of the `HttpClient`. + +```php +$client = new \Http\Adapter\Guzzle6\Client(); +$mailgun = new \Mailgun\Mailgun('api_key', $client); +``` + +You could also rely on the [auto discovery feature of Httplug](http://docs.php-http.org/en/latest/discovery.html). This +means that you need to install `puli/composer-plugin` and put a puli.phar in your project root. + **For shared hosts without SSH access, check out our [Shared Host Instructions](SharedHostInstall.md).** @@ -141,7 +161,9 @@ Go to http://bin.mailgun.net. The Postbin will generate a special URL. Save that ```php # First, instantiate the SDK with your API credentials and define your domain. -$mg = new Mailgun('key-example', 'bin.mailgun.net', 'aecf68de', $ssl = False); +$mg = new Mailgun('key-example', null, 'bin.mailgun.net'); +$mg->setApiVersion('aecf68de'); +$mg->setSslEnabled('false'); $domain = 'example.com'; # Now, compose and send your message. diff --git a/composer.json b/composer.json index 61bc460..6e8618b 100644 --- a/composer.json +++ b/composer.json @@ -2,11 +2,15 @@ "name": "mailgun/mailgun-php", "description": "The Mailgun SDK provides methods for all API functions.", "require": { - "guzzlehttp/guzzle": "~5.0" + "php": "^5.5|^7.0", + "guzzlehttp/psr7": "~1.2", + "php-http/httplug": "^1.0", + "php-http/client-implementation": "^1.0", + "php-http/discovery": "^0.8" }, "require-dev": { - "php": ">=5.4.0", - "phpunit/phpunit": "~4.6" + "phpunit/phpunit": "~4.6", + "php-http/guzzle6-adapter": "^1.0" }, "autoload": { "psr-0": { diff --git a/src/Mailgun/Connection/RestClient.php b/src/Mailgun/Connection/RestClient.php index 02e65db..1cdc2ee 100644 --- a/src/Mailgun/Connection/RestClient.php +++ b/src/Mailgun/Connection/RestClient.php @@ -2,17 +2,17 @@ namespace Mailgun\Connection; -use GuzzleHttp\Client as Guzzle; -use GuzzleHttp\Message\ResponseInterface; -use GuzzleHttp\Post\PostBodyInterface; -use GuzzleHttp\Post\PostFile; -use GuzzleHttp\Query; +use GuzzleHttp\Psr7\MultipartStream; +use GuzzleHttp\Psr7\Request; +use Http\Client\HttpClient; +use Http\Discovery\HttpClientDiscovery; use Mailgun\Connection\Exceptions\GenericHTTPError; use Mailgun\Connection\Exceptions\InvalidCredentials; use Mailgun\Connection\Exceptions\MissingRequiredParameters; use Mailgun\Connection\Exceptions\MissingEndpoint; use Mailgun\Constants\Api; use Mailgun\Constants\ExceptionMessages; +use Psr\Http\Message\ResponseInterface; /** * This class is a wrapper for the Guzzle (HTTP Client Library). @@ -20,35 +20,73 @@ use Mailgun\Constants\ExceptionMessages; class RestClient { /** + * Your API key * @var string */ private $apiKey; /** - * @var Guzzle + * @var HttpClient */ - protected $mgClient; + protected $httpClient; /** - * @param string $apiKey - * @param string $apiEndpoint - * @param string $apiVersion - * @param bool $ssl + * @var string */ - public function __construct($apiKey, $apiEndpoint, $apiVersion, $ssl) + protected $apiHost; + + /** + * The version of the API to use + * @var string + */ + protected $apiVersion = 'v2'; + + /** + * If we should use SSL or not + * @var bool + */ + protected $sslEnabled = true; + + /** + * @param string $apiKey + * @param string $apiHost + * @param HttpClient $httpClient + */ + public function __construct($apiKey, $apiHost, HttpClient $httpClient = null) { $this->apiKey = $apiKey; - $this->mgClient = new Guzzle([ - 'base_url'=>$this->generateEndpoint($apiEndpoint, $apiVersion, $ssl), - 'defaults'=>[ - 'auth' => array(Api::API_USER, $this->apiKey), - 'exceptions' => false, - 'config' => ['curl' => [ CURLOPT_FORBID_REUSE => true ]], - 'headers' => [ - 'User-Agent' => Api::SDK_USER_AGENT.'/'.Api::SDK_VERSION, - ], - ], - ]); + $this->apiHost = $apiHost; + $this->httpClient = $httpClient; + } + + /** + * @param string $method + * @param string $uri + * @param array $body + * @param array $files + * @param array $headers + * + * @return \stdClass + * + * @throws GenericHTTPError + * @throws InvalidCredentials + * @throws MissingEndpoint + * @throws MissingRequiredParameters + */ + protected function send($method, $uri, $body = null, $files = [], array $headers = []) + { + $headers['User-Agent'] = Api::SDK_USER_AGENT.'/'.Api::SDK_VERSION; + $headers['Authorization'] = 'Basic '.base64_encode(sprintf('%s:%s', Api::API_USER, $this->apiKey)); + + if (!empty($files)) { + $body = new MultipartStream($files); + $headers['Content-Type'] = 'multipart/form-data; boundary='.$body->getBoundary(); + } + + $request = new Request($method, $this->getApiUrl($uri), $headers, $body); + $response = $this->getHttpClient()->sendRequest($request); + + return $this->responseHandler($response); } /** @@ -65,27 +103,39 @@ class RestClient */ public function post($endpointUrl, $postData = array(), $files = array()) { - $request = $this->mgClient->createRequest('POST', $endpointUrl, ['body' => $postData]); - /** @var \GuzzleHttp\Post\PostBodyInterface $postBody */ - $postBody = $request->getBody(); - $postBody->setAggregator(Query::duplicateAggregator()); + $postFiles = []; $fields = ['message', 'attachment', 'inline']; foreach ($fields as $fieldName) { if (isset($files[$fieldName])) { if (is_array($files[$fieldName])) { foreach ($files[$fieldName] as $file) { - $this->addFile($postBody, $fieldName, $file); + $postFiles[] = $this->prepareFile($fieldName, $file); } } else { - $this->addFile($postBody, $fieldName, $files[$fieldName]); + $postFiles[] = $this->prepareFile($fieldName, $files[$fieldName]); } } } - $response = $this->mgClient->send($request); + $postDataMultipart = []; + foreach ($postData as $key => $value) { + if (is_array($value)) { + foreach ($value as $subValue) { + $postDataMultipart[] = [ + 'name' => $key, + 'contents' => $subValue, + ]; + } + } else { + $postDataMultipart[] = [ + 'name' => $key, + 'contents' => $value, + ]; + } + } - return $this->responseHandler($response); + return $this->send('POST', $endpointUrl, [], array_merge($postDataMultipart, $postFiles)); } /** @@ -101,9 +151,7 @@ class RestClient */ public function get($endpointUrl, $queryString = array()) { - $response = $this->mgClient->get($endpointUrl, ['query' => $queryString]); - - return $this->responseHandler($response); + return $this->send('GET', $endpointUrl.'?'.http_build_query($queryString)); } /** @@ -118,9 +166,7 @@ class RestClient */ public function delete($endpointUrl) { - $response = $this->mgClient->delete($endpointUrl); - - return $this->responseHandler($response); + return $this->send('DELETE', $endpointUrl); } /** @@ -136,14 +182,7 @@ class RestClient */ public function put($endpointUrl, $putData) { - $request = $this->mgClient->createRequest('PUT', $endpointUrl, ['body' => $putData]); - /** @var \GuzzleHttp\Post\PostBodyInterface $postBody */ - $postBody = $request->getBody(); - $postBody->setAggregator(Query::duplicateAggregator()); - - $response = $this->mgClient->send($request); - - return $this->responseHandler($response); + return $this->send('PUT', $endpointUrl, $putData); } /** @@ -156,7 +195,7 @@ class RestClient * @throws MissingEndpoint * @throws MissingRequiredParameters */ - public function responseHandler($responseObj) + public function responseHandler(ResponseInterface $responseObj) { $httpResponseCode = $responseObj->getStatusCode(); if ($httpResponseCode === 200) { @@ -180,43 +219,26 @@ class RestClient } /** - * @param \Guzzle\Http\Message\Response $responseObj + * @param ResponseInterface $responseObj * * @return string */ - protected function getResponseExceptionMessage(\GuzzleHttp\Message\Response $responseObj) + protected function getResponseExceptionMessage(ResponseInterface $responseObj) { $body = (string) $responseObj->getBody(); $response = json_decode($body); if (json_last_error() == JSON_ERROR_NONE && isset($response->message)) { - return " ".$response->message; + return ' '.$response->message; } } /** - * @param string $apiEndpoint - * @param string $apiVersion - * @param bool $ssl + * Prepare a file for the postBody. * - * @return string + * @param string $fieldName + * @param string|array $filePath */ - private function generateEndpoint($apiEndpoint, $apiVersion, $ssl) - { - if (!$ssl) { - return "http://".$apiEndpoint."/".$apiVersion."/"; - } else { - return "https://".$apiEndpoint."/".$apiVersion."/"; - } - } - - /** - * Add a file to the postBody. - * - * @param PostBodyInterface $postBody - * @param string $fieldName - * @param string|array $filePath - */ - private function addFile(PostBodyInterface $postBody, $fieldName, $filePath) + protected function prepareFile($fieldName, $filePath) { $filename = null; // Backward compatibility code @@ -230,6 +252,75 @@ class RestClient $filePath = substr($filePath, 1); } - $postBody->addFile(new PostFile($fieldName, fopen($filePath, 'r'), $filename)); + return [ + 'name' => $fieldName, + 'contents' => fopen($filePath, 'r'), + 'filename' => $filename, + ]; + } + + + /** + * + * @return HttpClient + */ + protected function getHttpClient() + { + if ($this->httpClient === null) { + $this->httpClient = HttpClientDiscovery::find(); + } + + return $this->httpClient; + } + + /** + * @param $uri + * + * @return string + */ + private function getApiUrl($uri) + { + return $this->generateEndpoint($this->apiHost, $this->apiVersion, $this->sslEnabled).$uri; + } + + + /** + * @param string $apiEndpoint + * @param string $apiVersion + * @param bool $ssl + * + * @return string + */ + private function generateEndpoint($apiEndpoint, $apiVersion, $ssl) + { + if (!$ssl) { + return 'http://'.$apiEndpoint.'/'.$apiVersion.'/'; + } else { + return 'https://'.$apiEndpoint.'/'.$apiVersion.'/'; + } + } + + /** + * @param string $apiVersion + * + * @return RestClient + */ + public function setApiVersion($apiVersion) + { + $this->apiVersion = $apiVersion; + + return $this; + } + + /** + * @param boolean $sslEnabled + * + * @return RestClient + */ + public function setSslEnabled($sslEnabled) + { + $this->sslEnabled = $sslEnabled; + + return $this; } } diff --git a/src/Mailgun/Mailgun.php b/src/Mailgun/Mailgun.php index f9921ef..6777801 100644 --- a/src/Mailgun/Mailgun.php +++ b/src/Mailgun/Mailgun.php @@ -2,6 +2,7 @@ namespace Mailgun; +use Http\Client\HttpClient; use Mailgun\Constants\ExceptionMessages; use Mailgun\Messages\Exceptions; use Mailgun\Connection\RestClient; @@ -29,13 +30,16 @@ class Mailgun{ /** * @param string|null $apiKey + * @param HttpClient $httpClient * @param string $apiEndpoint - * @param string $apiVersion - * @param bool $ssl */ - public function __construct($apiKey = null, $apiEndpoint = "api.mailgun.net", $apiVersion = "v3", $ssl = true){ + public function __construct( + $apiKey = null, + HttpClient $httpClient = null, + $apiEndpoint = 'api.mailgun.net' + ) { $this->apiKey = $apiKey; - $this->restClient = new RestClient($apiKey, $apiEndpoint, $apiVersion, $ssl); + $this->restClient = new RestClient($apiKey, $apiEndpoint, $httpClient); } /** @@ -132,6 +136,30 @@ class Mailgun{ return $this->restClient->put($endpointUrl, $putData); } + /** + * @param string $apiVersion + * + * @return Mailgun + */ + public function setApiVersion($apiVersion) + { + $this->restClient->setApiVersion($apiVersion); + + return $this; + } + + /** + * @param boolean $sslEnabled + * + * @return Mailgun + */ + public function setSslEnabled($sslEnabled) + { + $this->restClient->setSslEnabled($sslEnabled); + + return $this; + } + /** * @return MessageBuilder */ diff --git a/tests/Mailgun/Tests/Connection/ConnectionTest.php b/tests/Mailgun/Tests/Connection/ConnectionTest.php deleted file mode 100644 index aed26de..0000000 --- a/tests/Mailgun/Tests/Connection/ConnectionTest.php +++ /dev/null @@ -1,19 +0,0 @@ -client = new Mailgun("My-Super-Awesome-API-Key", "samples.mailgun.org", false); - } -} diff --git a/tests/Mailgun/Tests/Messages/MessageBuilderTest.php b/tests/Mailgun/Tests/Messages/MessageBuilderTest.php index 4a46eea..8daf2e3 100644 --- a/tests/Mailgun/Tests/Messages/MessageBuilderTest.php +++ b/tests/Mailgun/Tests/Messages/MessageBuilderTest.php @@ -9,7 +9,7 @@ class MessageBuilderTest extends \Mailgun\Tests\MailgunTestCase public function setUp() { - $this->client = new Mailgun("My-Super-Awesome-API-Key", "samples.mailgun.org", false); + $this->client = new Mailgun(); } public function testBlankInstantiation() diff --git a/tests/Mailgun/Tests/Mock/Connection/TestBroker.php b/tests/Mailgun/Tests/Mock/Connection/TestBroker.php index a9ee3b0..6652aa5 100644 --- a/tests/Mailgun/Tests/Mock/Connection/TestBroker.php +++ b/tests/Mailgun/Tests/Mock/Connection/TestBroker.php @@ -13,10 +13,10 @@ class TestBroker extends RestClient protected $apiEndpoint; - public function __construct($apiKey = null, $apiEndpoint = "api.mailgun.net", $apiVersion = "v3") + public function __construct($apiKey = null, $apiHost = "api.mailgun.net", $apiVersion = "v3") { $this->apiKey = $apiKey; - $this->apiEndpoint = $apiEndpoint; + $this->apiEndpoint = $apiHost; } public function post($endpointUrl, $postData = array(), $files = array())