├── examples ├── bootstrap.php ├── transmissions │ ├── sparkpost.png │ ├── get_transmission.php │ ├── get_all_transmissions.php │ ├── delete_transmission.php │ ├── create_transmission_with_template.php │ ├── create_transmission_with_recipient_list.php │ ├── create_transmission.php │ ├── create_transmission_with_attachment.php │ └── create_transmission_with_cc_and_bcc.php ├── templates │ ├── get_all_templates.php │ ├── delete_template.php │ ├── get_template.php │ ├── update_template.php │ ├── preview_template.php │ └── create_template.php ├── message-events │ ├── get_message_events.php │ └── get_message_events_with_retry_logic.php └── debug │ └── index.php ├── .coveralls.yml ├── lib └── SparkPost │ ├── Resource.php │ ├── SparkPostException.php │ ├── SparkPostPromise.php │ ├── ResourceBase.php │ ├── SparkPostResponse.php │ ├── Transmission.php │ └── SparkPost.php ├── .gitignore ├── .editorconfig ├── .travis.yml ├── AUTHORS.md ├── phpunit.xml.dist ├── composer.json ├── LICENSE.txt ├── MIGRATION.md ├── CONTRIBUTING.md ├── test └── unit │ ├── SparkPostResponseTest.php │ ├── TransmissionTest.php │ └── SparkPostTest.php ├── CHANGELOG.md └── README.md /examples/bootstrap.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | $promise = $sparky->request('GET', 'templates'); 17 | 18 | try { 19 | $response = $promise->wait(); 20 | echo $response->getStatusCode()."\n"; 21 | print_r($response->getBody())."\n"; 22 | } catch (\Exception $e) { 23 | echo $e->getCode()."\n"; 24 | echo $e->getMessage()."\n"; 25 | } 26 | -------------------------------------------------------------------------------- /examples/templates/delete_template.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | $template_id = "PHP-example-template"; 17 | 18 | $promise = $sparky->request('DELETE', "templates/$template_id"); 19 | 20 | try { 21 | $response = $promise->wait(); 22 | echo $response->getStatusCode()."\n"; 23 | print_r($response->getBody())."\n"; 24 | } catch (\Exception $e) { 25 | echo $e->getCode()."\n"; 26 | echo $e->getMessage()."\n"; 27 | } 28 | -------------------------------------------------------------------------------- /examples/templates/get_template.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | $template_id = "PHP-example-template"; 17 | 18 | $promise = $sparky->request('GET', "templates/$template_id?draft=true"); 19 | 20 | try { 21 | $response = $promise->wait(); 22 | echo $response->getStatusCode()."\n"; 23 | print_r($response->getBody())."\n"; 24 | } catch (\Exception $e) { 25 | echo $e->getCode()."\n"; 26 | echo $e->getMessage()."\n"; 27 | } 28 | -------------------------------------------------------------------------------- /examples/message-events/get_message_events.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | // New endpoint - https://developers.sparkpost.com/api/events/ 17 | $promise = $sparky->request('GET', 'events/message', [ 18 | 'campaign_ids' => 'CAMPAIGN_ID', 19 | ]); 20 | 21 | try { 22 | $response = $promise->wait(); 23 | echo $response->getStatusCode()."\n"; 24 | print_r($response->getBody())."\n"; 25 | } catch (\Exception $e) { 26 | echo $e->getCode()."\n"; 27 | echo $e->getMessage()."\n"; 28 | } 29 | -------------------------------------------------------------------------------- /examples/templates/update_template.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | $template_id = "PHP-example-template"; 17 | 18 | $promise = $sparky->request('PUT', "templates/$template_id", [ 19 | 'options' => [ 20 | 'open_tracking' => true, 21 | ], 22 | ]); 23 | 24 | try { 25 | $response = $promise->wait(); 26 | echo $response->getStatusCode()."\n"; 27 | print_r($response->getBody())."\n"; 28 | } catch (\Exception $e) { 29 | echo $e->getCode()."\n"; 30 | echo $e->getMessage()."\n"; 31 | } 32 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | php-sparkpost is maintained by Message Systems. 2 | 3 | # Contributors 4 | 5 | * Jordan Nornhold, [@beardyman](https://github.com/beardyman) 6 | * Rich Leland, [@richleland](https://github.com/richleland) 7 | * Jason Rhodes [@jasonrhodes](https://github.com/jasonrhodes) 8 | * Matthew April, [@MattApril](https://github.com/MattApril) 9 | * James Fellows, [@j4m3s](https://github.com/j4m3s) 10 | * LF Bittencourt, [@lfbittencourt](https://github.com/lfbittencourt) 11 | * Jakub Piasecki, [@zaporylie](https://github.com/zaporylie) 12 | * Danil Zakablukovskiy, [@djagya](https://github.com/djagya) 13 | * Chris Wilson, [@yepher](https://github.com/yepher) 14 | * Maxim Dzhuliy, [@max-si-m](https://github.com/max-si-m) 15 | * [@chandon](https://github.com/chandon) 16 | * Avi Goldman, [@avrahamgoldman](https://github.com/avrahamgoldman) 17 | * Vincent Song, [@vwsong](https://github.com/vwsong) 18 | * Tobias Nyholm, [@Nyholm](https://github.com/Nyholm) 19 | -------------------------------------------------------------------------------- /examples/templates/preview_template.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | $template_id = "PHP-example-template"; 17 | 18 | $promise = $sparky->request('POST', "templates/$template_id/preview?draft=true", [ 19 | 'substitution_data' => [ 20 | 'some_key' => 'some_value', 21 | ], 22 | ]); 23 | 24 | try { 25 | $response = $promise->wait(); 26 | echo $response->getStatusCode()."\n"; 27 | print_r($response->getBody())."\n"; 28 | } catch (\Exception $e) { 29 | echo $e->getCode()."\n"; 30 | echo $e->getMessage()."\n"; 31 | } 32 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./lib 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ./test/unit 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/transmissions/delete_transmission.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | // Delete *scheduled* transmissions (only) by *campaign ID* (only) 17 | // See https://developers.sparkpost.com/api/transmissions/#transmissions-delete-delete-a-scheduled-transmission 18 | 19 | $promise = $sparky->transmissions->delete('?campaign_id=white_christmas'); 20 | 21 | try { 22 | $response = $promise->wait(); 23 | echo $response->getStatusCode()."\n"; 24 | print_r($response->getBody())."\n"; 25 | } catch (\Exception $e) { 26 | echo $e->getCode()."\n"; 27 | echo $e->getMessage()."\n"; 28 | } 29 | -------------------------------------------------------------------------------- /examples/debug/index.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY'), 18 | // fetch API KEY from environment variable 19 | "debug" => true 20 | ]); 21 | 22 | $promise = $sparky->request('GET', 'templates'); 23 | 24 | try { 25 | $response = $promise->wait(); 26 | 27 | var_dump($response); 28 | 29 | echo "Request:\n"; 30 | print_r($response->getRequest()); 31 | 32 | echo "Response:\n"; 33 | echo $response->getStatusCode()."\n"; 34 | print_r($response->getBody())."\n"; 35 | } catch (\Exception $e) { 36 | echo "Request:\n"; 37 | print_r($e->getRequest()); 38 | 39 | echo "Exception:\n"; 40 | echo $e->getCode()."\n"; 41 | echo $e->getMessage()."\n"; 42 | } 43 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sparkpost/sparkpost", 3 | "description": "Client library for interfacing with the SparkPost API.", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "SparkPost" 8 | } 9 | ], 10 | "minimum-stability": "stable", 11 | "scripts": { 12 | "test": "XDEBUG_MODE=coverage ./vendor/bin/phpunit", 13 | "fix-style": "php-cs-fixer fix ." 14 | }, 15 | "require": { 16 | "php": "^7.1 || ^8.0", 17 | "php-http/httplug": "^1.0 || ^2.0", 18 | "php-http/message": "^1.0", 19 | "php-http/client-implementation": "^1.0", 20 | "php-http/discovery": "^1.0" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "^8.0 || ^9.0", 24 | "php-http/guzzle6-adapter": "^1.0", 25 | "mockery/mockery": "^1.3", 26 | "nyholm/nsa": "^1.0", 27 | "php-coveralls/php-coveralls": "^2.4", 28 | "friendsofphp/php-cs-fixer": "^2.18" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "SparkPost\\": "lib/SparkPost" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "SparkPost\\Test\\": "test/unit" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/message-events/get_message_events_with_retry_logic.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY'), "retries" => 3]); 15 | 16 | // New endpoint - https://developers.sparkpost.com/api/events/ 17 | $promise = $sparky->request('GET', 'events/message', [ 18 | 'campaign_ids' => 'CAMPAIGN_ID', 19 | ]); 20 | 21 | /** 22 | * If this fails with a 5xx it will have failed 4 times 23 | */ 24 | try { 25 | $response = $promise->wait(); 26 | echo $response->getStatusCode()."\n"; 27 | print_r($response->getBody())."\n"; 28 | } catch (\Exception $e) { 29 | echo $e->getCode()."\n"; 30 | echo $e->getMessage()."\n"; 31 | 32 | if ($e->getCode() >= 500 && $e->getCode() <= 599) { 33 | echo "Wow, this failed epically"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014 Message Systems, Inc. or its affiliates. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /examples/transmissions/create_transmission_with_template.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | // put your own sending domain and test recipient address here 17 | $sending_domain = "steve2-test.trymsys.net"; 18 | $your_email = "bob@sink.sparkpostmail.com"; 19 | 20 | $template_id = "PHP-example-template"; 21 | 22 | $promise = $sparky->transmissions->post([ 23 | 'content' => ['template_id' => $template_id], 24 | 'substitution_data' => ['name' => 'YOUR_FIRST_NAME'], 25 | 'recipients' => [ 26 | [ 27 | 'address' => [ 28 | 'name' => 'YOUR_NAME', 29 | 'email' => $your_email, 30 | ], 31 | ], 32 | ], 33 | ]); 34 | 35 | try { 36 | $response = $promise->wait(); 37 | echo $response->getStatusCode()."\n"; 38 | print_r($response->getBody())."\n"; 39 | } catch (\Exception $e) { 40 | echo $e->getCode()."\n"; 41 | echo $e->getMessage()."\n"; 42 | } 43 | -------------------------------------------------------------------------------- /examples/transmissions/create_transmission_with_recipient_list.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | // put your own sending domain and test recipient address here 17 | $sending_domain = "steve2-test.trymsys.net"; 18 | 19 | // The ID of a list in your SparkPost account 20 | $my_list = "mylist1"; 21 | 22 | $promise = $sparky->transmissions->post([ 23 | 'content' => [ 24 | 'from' => [ 25 | 'name' => 'SparkPost Team', 26 | 'email' => "from@$sending_domain", 27 | ], 28 | 'subject' => 'Mailing With Recipient List From PHP', 29 | 'html' => '

Congratulations, {{name}}!

You just sent an email to everyone on your recipient list!

', 30 | 'text' => 'Congratulations, {{name}}! You just sent an email to everyone on your recipient list!', 31 | ], 32 | 'substitution_data' => ['name' => 'YOUR_FIRST_NAME'], 33 | 'recipients' => ['list_id' => $my_list], 34 | ]); 35 | 36 | try { 37 | $response = $promise->wait(); 38 | echo $response->getStatusCode()."\n"; 39 | print_r($response->getBody())."\n"; 40 | } catch (\Exception $e) { 41 | echo $e->getCode()."\n"; 42 | echo $e->getMessage()."\n"; 43 | } 44 | -------------------------------------------------------------------------------- /examples/transmissions/create_transmission.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | // put your own sending domain and test recipient address here 17 | $sending_domain = "steve2-test.trymsys.net"; 18 | $your_email = "bob@sink.sparkpostmail.com"; 19 | 20 | $promise = $sparky->transmissions->post([ 21 | 'content' => [ 22 | 'from' => [ 23 | 'name' => 'SparkPost Team', 24 | 'email' => "from@$sending_domain", 25 | ], 26 | 'subject' => 'First Mailing From PHP', 27 | 'html' => '

Congratulations, {{name}}!

You just sent your very first mailing!

', 28 | 'text' => 'Congratulations, {{name}}! You just sent your very first mailing!', 29 | ], 30 | 'substitution_data' => ['name' => 'YOUR_FIRST_NAME'], 31 | 'recipients' => [ 32 | [ 33 | 'address' => [ 34 | 'name' => 'YOUR_NAME', 35 | 'email' => $your_email, 36 | ], 37 | ], 38 | ], 39 | ]); 40 | 41 | try { 42 | $response = $promise->wait(); 43 | echo $response->getStatusCode()."\n"; 44 | print_r($response->getBody())."\n"; 45 | } catch (\Exception $e) { 46 | echo $e->getCode()."\n"; 47 | echo $e->getMessage()."\n"; 48 | } 49 | -------------------------------------------------------------------------------- /lib/SparkPost/SparkPostException.php: -------------------------------------------------------------------------------- 1 | request = $request; 27 | 28 | $message = $exception->getMessage(); 29 | $code = $exception->getCode(); 30 | if ($exception instanceof HttpException) { 31 | $message = $exception->getResponse()->getBody()->__toString(); 32 | $this->body = json_decode($message, true); 33 | $code = $exception->getResponse()->getStatusCode(); 34 | } 35 | 36 | parent::__construct($message, $code, $exception->getPrevious()); 37 | } 38 | 39 | /** 40 | * Returns the request values sent. 41 | * 42 | * @return array $request 43 | */ 44 | public function getRequest() 45 | { 46 | return $this->request; 47 | } 48 | 49 | /** 50 | * Returns the body. 51 | * 52 | * @return array $body - the json decoded body from the http response 53 | */ 54 | public function getBody() 55 | { 56 | return $this->body; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/templates/create_template.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | $template_name = "PHP example template"; 17 | $template_id = "PHP-example-template"; 18 | 19 | // put your own sending domain here 20 | $sending_domain = "steve2-test.trymsys.net"; 21 | 22 | // Valid short template content examples 23 | $plain_text = 'Write your text message part here.'; 24 | 25 | $html = << 27 | 28 | 29 |

Write your HTML message part here

30 | 31 | 32 | HTML; 33 | 34 | $amp_html = << 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Hello World! Let's get started using AMP HTML together! 44 | 45 | 46 | HTML; 47 | 48 | $promise = $sparky->request('POST', 'templates', [ 49 | 'name' => $template_name, 50 | 'id' => $template_id, 51 | 'content' => [ 52 | 'from' => "from@$sending_domain", 53 | 'subject' => 'Your Subject', 54 | 'text' => $plain_text, 55 | 'html' => $html, 56 | 'amp_html' => $amp_html, 57 | ], 58 | ]); 59 | 60 | try { 61 | $response = $promise->wait(); 62 | echo $response->getStatusCode()."\n"; 63 | print_r($response->getBody())."\n"; 64 | } catch (\Exception $e) { 65 | echo $e->getCode()."\n"; 66 | echo $e->getMessage()."\n"; 67 | } 68 | -------------------------------------------------------------------------------- /examples/transmissions/create_transmission_with_attachment.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | $filePath = dirname(__FILE__).'/'; 17 | $fileName = 'sparkpost.png'; 18 | $fileType = mime_content_type($filePath.$fileName); 19 | $fileData = base64_encode(file_get_contents($filePath.$fileName)); 20 | 21 | // put your own sending domain and test recipient address here 22 | $sending_domain = "steve2-test.trymsys.net"; 23 | $your_email = "bob@sink.sparkpostmail.com"; 24 | 25 | $promise = $sparky->transmissions->post([ 26 | 'content' => [ 27 | 'from' => [ 28 | 'name' => 'SparkPost Team', 29 | 'email' => "from@$sending_domain", 30 | ], 31 | 'subject' => 'Mailing With Attachment From PHP', 32 | 'html' => '

Congratulations, {{name}}!

You just sent an email with an attachment!

', 33 | 'text' => 'Congratulations, {{name}}! You just sent an email with an attachment', 34 | 'attachments' => [ 35 | [ 36 | 'name' => $fileName, 37 | 'type' => $fileType, 38 | 'data' => $fileData, 39 | ], 40 | ], 41 | ], 42 | 'substitution_data' => ['name' => 'YOUR_FIRST_NAME'], 43 | 'recipients' => [ 44 | [ 45 | 'address' => [ 46 | 'name' => 'YOUR_NAME', 47 | 'email' => $your_email, 48 | ], 49 | ], 50 | ], 51 | ]); 52 | 53 | try { 54 | $response = $promise->wait(); 55 | echo $response->getStatusCode()."\n"; 56 | print_r($response->getBody())."\n"; 57 | } catch (\Exception $e) { 58 | echo $e->getCode()."\n"; 59 | echo $e->getMessage()."\n"; 60 | } 61 | -------------------------------------------------------------------------------- /examples/transmissions/create_transmission_with_cc_and_bcc.php: -------------------------------------------------------------------------------- 1 | getenv('SPARKPOST_API_KEY')]); 15 | 16 | // put your own sending domain and test recipient address here 17 | $sending_domain = "steve2-test.trymsys.net"; 18 | $your_email = "bob@sink.sparkpostmail.com"; 19 | $your_cc = "alice@sink.sparkpostmail.com"; 20 | $your_bcc = "charles@sink.sparkpostmail.com"; 21 | 22 | $promise = $sparky->transmissions->post([ 23 | 'content' => [ 24 | 'from' => [ 25 | 'name' => 'SparkPost Team', 26 | 'email' => "from@$sending_domain", 27 | ], 28 | 'subject' => 'Mailing With CC and BCC From PHP', 29 | 'html' => '

Congratulations, {{name}}!

You just sent your very first mailing with CC and BCC recipients!

', 30 | 'text' => 'Congratulations, {{name}}! You just sent your very first mailing with CC and BCC recipients!', 31 | ], 32 | 'substitution_data' => ['name' => 'YOUR_FIRST_NAME'], 33 | 'recipients' => [ 34 | [ 35 | 'address' => [ 36 | 'name' => 'YOUR_NAME', 37 | 'email' => $your_email, 38 | ], 39 | ], 40 | ], 41 | 'cc' => [ 42 | [ 43 | 'address' => [ 44 | 'name' => 'ANOTHER_NAME', 45 | 'email' => $your_cc, 46 | ], 47 | ], 48 | ], 49 | 'bcc' => [ 50 | [ 51 | 'address' => [ 52 | 'name' => 'AND_ANOTHER_NAME', 53 | 'email' => $your_bcc, 54 | ], 55 | ], 56 | ], 57 | ]); 58 | 59 | try { 60 | $response = $promise->wait(); 61 | echo $response->getStatusCode()."\n"; 62 | print_r($response->getBody())."\n"; 63 | } catch (\Exception $e) { 64 | echo $e->getCode()."\n"; 65 | echo $e->getMessage()."\n"; 66 | } 67 | -------------------------------------------------------------------------------- /lib/SparkPost/SparkPostPromise.php: -------------------------------------------------------------------------------- 1 | promise = $promise; 27 | $this->request = $request; 28 | } 29 | 30 | /** 31 | * Hand off the response functions to the original promise and return a custom response or exception. 32 | * 33 | * @param callable $onFulfilled - function to be called if the promise is fulfilled 34 | * @param callable $onRejected - function to be called if the promise is rejected 35 | */ 36 | public function then(callable $onFulfilled = null, callable $onRejected = null) 37 | { 38 | $request = $this->request; 39 | 40 | return $this->promise->then(function ($response) use ($onFulfilled, $request) { 41 | if (isset($onFulfilled)) { 42 | $onFulfilled(new SparkPostResponse($response, $request)); 43 | } 44 | }, function ($exception) use ($onRejected, $request) { 45 | if (isset($onRejected)) { 46 | $onRejected(new SparkPostException($exception, $request)); 47 | } 48 | }); 49 | } 50 | 51 | /** 52 | * Hand back the state. 53 | * 54 | * @return $state - returns the state of the promise 55 | */ 56 | public function getState() 57 | { 58 | return $this->promise->getState(); 59 | } 60 | 61 | /** 62 | * Wraps the wait function and returns a custom response or throws a custom exception. 63 | * 64 | * @param bool $unwrap 65 | * 66 | * @return SparkPostResponse 67 | * 68 | * @throws SparkPostException 69 | */ 70 | public function wait($unwrap = true) 71 | { 72 | try { 73 | $response = $this->promise->wait($unwrap); 74 | 75 | return $response ? new SparkPostResponse($response, $this->request) : $response; 76 | } catch (\Exception $exception) { 77 | throw new SparkPostException($exception, $this->request); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/SparkPost/ResourceBase.php: -------------------------------------------------------------------------------- 1 | sparkpost = $sparkpost; 29 | $this->endpoint = $endpoint; 30 | } 31 | 32 | /** 33 | * Sends get request to API at the set endpoint. 34 | * 35 | * @see SparkPost->request() 36 | */ 37 | public function get($uri = '', $payload = [], $headers = []) 38 | { 39 | return $this->request('GET', $uri, $payload, $headers); 40 | } 41 | 42 | /** 43 | * Sends put request to API at the set endpoint. 44 | * 45 | * @see SparkPost->request() 46 | */ 47 | public function put($uri = '', $payload = [], $headers = []) 48 | { 49 | return $this->request('PUT', $uri, $payload, $headers); 50 | } 51 | 52 | /** 53 | * Sends post request to API at the set endpoint. 54 | * 55 | * @see SparkPost->request() 56 | */ 57 | public function post($payload = [], $headers = []) 58 | { 59 | return $this->request('POST', '', $payload, $headers); 60 | } 61 | 62 | /** 63 | * Sends delete request to API at the set endpoint. 64 | * 65 | * @see SparkPost->request() 66 | */ 67 | public function delete($uri = '', $payload = [], $headers = []) 68 | { 69 | return $this->request('DELETE', $uri, $payload, $headers); 70 | } 71 | 72 | /** 73 | * Sends requests to SparkPost object to the resource endpoint. 74 | * 75 | * @see SparkPost->request() 76 | * 77 | * @return SparkPostPromise or SparkPostResponse depending on sync or async request 78 | */ 79 | public function request($method = 'GET', $uri = '', $payload = [], $headers = []) 80 | { 81 | if (is_array($uri)) { 82 | $headers = $payload; 83 | $payload = $uri; 84 | $uri = ''; 85 | } 86 | 87 | $uri = $this->endpoint.'/'.$uri; 88 | 89 | return $this->sparkpost->request($method, $uri, $payload, $headers); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/SparkPost/SparkPostResponse.php: -------------------------------------------------------------------------------- 1 | response = $response; 28 | $this->request = $request; 29 | } 30 | 31 | /** 32 | * Returns the request values sent. 33 | * 34 | * @return array $request 35 | */ 36 | public function getRequest() 37 | { 38 | return $this->request; 39 | } 40 | 41 | /** 42 | * Returns the body. 43 | * 44 | * @return array $body - the json decoded body from the http response 45 | */ 46 | public function getBody() 47 | { 48 | $body = $this->response->getBody(); 49 | $body_string = $body->__toString(); 50 | 51 | $json = json_decode($body_string, true); 52 | 53 | return $json; 54 | } 55 | 56 | /** 57 | * pass these down to the response given in the constructor. 58 | */ 59 | public function getProtocolVersion() 60 | { 61 | return $this->response->getProtocolVersion(); 62 | } 63 | 64 | public function withProtocolVersion($version) 65 | { 66 | return $this->response->withProtocolVersion($version); 67 | } 68 | 69 | public function getHeaders() 70 | { 71 | return $this->response->getHeaders(); 72 | } 73 | 74 | public function hasHeader($name) 75 | { 76 | return $this->response->hasHeader($name); 77 | } 78 | 79 | public function getHeader($name) 80 | { 81 | return $this->response->getHeader($name); 82 | } 83 | 84 | public function getHeaderLine($name) 85 | { 86 | return $this->response->getHeaderLine($name); 87 | } 88 | 89 | public function withHeader($name, $value) 90 | { 91 | return $this->response->withHeader($name, $value); 92 | } 93 | 94 | public function withAddedHeader($name, $value) 95 | { 96 | return $this->response->withAddedHeader($name, $value); 97 | } 98 | 99 | public function withoutHeader($name) 100 | { 101 | return $this->response->withoutHeader($name); 102 | } 103 | 104 | public function withBody(StreamInterface $body) 105 | { 106 | return $this->response->withBody($body); 107 | } 108 | 109 | public function getStatusCode() 110 | { 111 | return $this->response->getStatusCode(); 112 | } 113 | 114 | public function withStatus($code, $reasonPhrase = '') 115 | { 116 | return $this->response->withStatus($code, $reasonPhrase); 117 | } 118 | 119 | public function getReasonPhrase() 120 | { 121 | return $this->response->getReasonPhrase(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Migration Guide 2 | 3 | This is a guide to help you make the switch when the SparkPost PHP library changes major versions. 4 | 5 | ## Migrating from 1.0 to 2.0 6 | 7 | ## Package name change 8 | The composer package name has changed from `sparkpost/php-sparkpost` to `sparkpost/sparkpost` 9 | 10 | ### No more setupUnwrapped 11 | We replaced the idea of 'wrapping' API resources with a simple `request` function. To see it in action, check out this [example](https://github.com/SparkPost/php-sparkpost/tree/2.0.0#send-an-api-call-using-the-base-request-function). 12 | 13 | ### `transmission` becomes `transmissions` 14 | Transmission endpoints are now under `$sparky->transmissions` instead of `$sparky->transmission` to map more directly to the [API docs](https://developers.sparkpost.com/api/). 15 | 16 | * We no longer map parameters to the API - we simplified. Instead custom mapping, now set the payload to match the API docs. 17 | * The exceptions to the previous statement are `cc` and `bcc`. They are helpers to make it easier to add cc and bcc recipients. [Example](https://github.com/SparkPost/php-sparkpost/tree/2.0.0#send-an-email-using-the-transmissions-endpoint) 18 | 19 | ### Switched from Ivory Http Adapter to HTTPlug 20 | Ivory Http Adapter was deprecated in favor of HTTPlug. 21 | 22 | ### Asynchronous support 23 | We addeded in support for [asynchronous calls](https://github.com/SparkPost/php-sparkpost/tree/2.0.0#asynchronous) (assuming your client supports it). 24 | 25 | ### Example 26 | #### 2.0 27 | ```php 28 | try { 29 | $sparky->setOptions([ 'async' => false ]); 30 | // Build your email and send it! 31 | $results = $sparky->transmissions->post([ 32 | 'content'=>[ 33 | 'from'=>[ 34 | 'name' => 'From Envelope', 35 | 'email' => 'from@sparkpostbox.com>' 36 | ], 37 | 'subject'=>'First Mailing From PHP', 38 | 'html'=>'

Congratulations, {{name}}!

You just sent your very first mailing!

', 39 | 'text'=>'Congratulations, {{name}}!! You just sent your very first mailing!', 40 | ], 41 | 'substitution_data'=>['name'=>'YOUR FIRST NAME'], 42 | 'recipients'=>[ 43 | [ 44 | 'address'=>[ 45 | 'name'=>'YOUR FULL NAME', 46 | 'email'=>'YOUR EMAIL ADDRESS' 47 | ] 48 | ] 49 | ] 50 | ]); 51 | echo 'Woohoo! You just sent your first mailing!'; 52 | } catch (\Exception $err) { 53 | echo 'Whoops! Something went wrong'; 54 | var_dump($err); 55 | } 56 | ``` 57 | 58 | #### 1.0 59 | ```php 60 | try { 61 | // Build your email and send it! 62 | $results = $sparky->transmission->send([ 63 | 'from'=>[ 64 | 'name' => 'From Envelope', 65 | 'email' => 'from@sparkpostbox.com>' 66 | ], 67 | 'html'=>'

Congratulations, {{name}}!

You just sent your very first mailing!

', 68 | 'text'=>'Congratulations, {{name}}!! You just sent your very first mailing!', 69 | 'substitutionData'=>['name'=>'YOUR FIRST NAME'], 70 | 'subject'=>'First Mailing From PHP', 71 | 'recipients'=>[ 72 | [ 73 | 'address'=>[ 74 | 'name'=>'YOUR FULL NAME', 75 | 'email'=>'YOUR EMAIL ADDRESS' 76 | ] 77 | ] 78 | ] 79 | ]); 80 | echo 'Woohoo! You just sent your first mailing!'; 81 | } catch (\Exception $err) { 82 | echo 'Whoops! Something went wrong'; 83 | var_dump($err); 84 | } 85 | ``` 86 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to php-sparkpost 2 | Transparency is one of our core values, and we encourage developers to contribute and become part of the SparkPost developer community. 3 | 4 | The following is a set of guidelines for contributing to php-sparkpost, 5 | which is hosted in the [SparkPost Organization](https://github.com/sparkpost) on GitHub. 6 | These are just guidelines, not rules, use your best judgment and feel free to 7 | propose changes to this document in a pull request. 8 | 9 | ## Submitting Issues 10 | * You can create an issue [here](https://github.com/sparkpost/php-sparkpost/issues/new), but 11 | before doing that please read the notes below on debugging and submitting issues, 12 | and include as many details as possible with your report. 13 | * Include the version of php-sparkpost you are using. 14 | * Perform a [cursory search](https://github.com/SparkPost/php-sparkpost/issues?q=is%3Aissue+is%3Aopen) 15 | to see if a similar issue has already been submitted. 16 | 17 | ## Development 18 | 19 | ### Setup (Getting the Tools) 20 | #### Install Composer 21 | ``` 22 | curl -sS https://getcomposer.org/installer | php 23 | ``` 24 | 25 | Add composer install directory to $PATH `~/.composer/vendor/bin/` 26 | 27 | ### phpenv 28 | 29 | [phpenv](https://github.com/phpenv/phpenv-installer) is useful for testing locally across different PHP versions. 30 | 31 | ### Developing your app against a local version of the SparkPost library 32 | 33 | If you're working on the library and your app together, you can tell Composer to get `php-sparkpost` from a local path. With a directory structure such as: 34 | 35 | home 36 | - php-sparkpost 37 | - my-app 38 | - composer.json 39 | - .. etc 40 | 41 | Use the following for `my-app/composer.json`: 42 | ```json 43 | { 44 | "name": "sparkpost/php_simple_email_send", 45 | "description": "a small test program to send an email", 46 | "repositories": [ 47 | { 48 | "type": "path", 49 | "url": "../php-sparkpost" 50 | } 51 | ], 52 | "require": { 53 | "php-http/guzzle6-adapter": "^1.1", 54 | "guzzlehttp/guzzle": "^6.0", 55 | "sparkpost/sparkpost": "dev-master" 56 | } 57 | } 58 | ``` 59 | 60 | 61 | ### Memory 62 | We recommend increasing PHP’s memory limit, by default it uses 128MB. We ran into some issues during local development without doing so. You can do this by editing your php.ini file and modifying `memory_limit`. We set ours to `memory_limit = 1024M`. 63 | 64 | #### Install XDebug for code coverage generation 65 | Follow the instructions at [xdebug.org](http://xdebug.org/wizard.php) 66 | 67 | #### Development Tool Resources 68 | * https://getcomposer.org/doc/00-intro.md#globally-on-osx-via-homebrew- 69 | * https://phpunit.de/manual/current/en/installation.html 70 | 71 | ### Local Development 72 | * Fork [this repository](http://github.com/SparkPost/php-sparkpost) 73 | * Clone your fork 74 | * Run `composer install` 75 | * Write code! 76 | 77 | ### Contribution Steps 78 | 79 | #### Guidelines 80 | 81 | - Provide documentation for any newly added code. 82 | - Provide tests for any newly added code. 83 | - Follow [PSR-2](http://www.php-fig.org/psr/psr-2/) (_will be auto-enforced by php-cs-fixer in a later step_) 84 | 85 | 1. Create a new branch named after the issue you’ll be fixing (include the issue number as the branch name, example: Issue in GH is #8 then the branch name should be ISSUE-8) 86 | 1. Write corresponding tests and code (only what is needed to satisfy the issue and tests please) 87 | * Include your tests in the 'test' directory in an appropriate test file 88 | * Write code to satisfy the tests 89 | 1. Ensure automated tests pass 90 | 1. Run `composer run-script fix-style` to enforce PSR-2 style 91 | 1. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to [AUTHORS](https://github.com/SparkPost/php-sparkpost/blob/master/AUTHORS.md). 92 | 93 | 94 | ### Testing 95 | Once you are setup for local development: 96 | * You can execute the unit tests using: `composer test` 97 | * You can view coverage information by viewing: `open test/output/report/index.html` 98 | 99 | ## Releasing 100 | 101 | * Update version in the [library](https://github.com/SparkPost/php-sparkpost/blob/eeb6ba971584fcc4c12fd69247c6b24df7827af5/lib/SparkPost/SparkPost.php#L16) during development. This is used in the `user_agent` of your requests. 102 | 103 | * Once it's been merged down, create a release tag in git. 104 | * Composer will automatically pickup the new tag and present it as a release. 105 | -------------------------------------------------------------------------------- /test/unit/SparkPostResponseTest.php: -------------------------------------------------------------------------------- 1 | returnValue = 'some_value_to_return'; 19 | $this->responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); 20 | } 21 | 22 | public function testGetProtocolVersion() 23 | { 24 | $this->responseMock->shouldReceive('getProtocolVersion')->andReturn($this->returnValue); 25 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 26 | $this->assertEquals($this->responseMock->getProtocolVersion(), $sparkpostResponse->getProtocolVersion()); 27 | } 28 | 29 | public function testWithProtocolVersion() 30 | { 31 | $param = 'protocol version'; 32 | 33 | $this->responseMock->shouldReceive('withProtocolVersion')->andReturn($this->returnValue); 34 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 35 | $this->assertEquals($this->responseMock->withProtocolVersion($param), $sparkpostResponse->withProtocolVersion($param)); 36 | } 37 | 38 | public function testGetHeaders() 39 | { 40 | $this->responseMock->shouldReceive('getHeaders')->andReturn($this->returnValue); 41 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 42 | $this->assertEquals($this->responseMock->getHeaders(), $sparkpostResponse->getHeaders()); 43 | } 44 | 45 | public function testHasHeader() 46 | { 47 | $param = 'header'; 48 | 49 | $this->responseMock->shouldReceive('hasHeader')->andReturn($this->returnValue); 50 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 51 | $this->assertEquals($this->responseMock->hasHeader($param), $sparkpostResponse->hasHeader($param)); 52 | } 53 | 54 | public function testGetHeader() 55 | { 56 | $param = 'header'; 57 | 58 | $this->responseMock->shouldReceive('getHeader')->andReturn($this->returnValue); 59 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 60 | $this->assertEquals($this->responseMock->getHeader($param), $sparkpostResponse->getHeader($param)); 61 | } 62 | 63 | public function testGetHeaderLine() 64 | { 65 | $param = 'header'; 66 | 67 | $this->responseMock->shouldReceive('getHeaderLine')->andReturn($this->returnValue); 68 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 69 | $this->assertEquals($this->responseMock->getHeaderLine($param), $sparkpostResponse->getHeaderLine($param)); 70 | } 71 | 72 | public function testWithHeader() 73 | { 74 | $param = 'header'; 75 | $param2 = 'value'; 76 | 77 | $this->responseMock->shouldReceive('withHeader')->andReturn($this->returnValue); 78 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 79 | $this->assertEquals($this->responseMock->withHeader($param, $param2), $sparkpostResponse->withHeader($param, $param2)); 80 | } 81 | 82 | public function testWithAddedHeader() 83 | { 84 | $param = 'header'; 85 | $param2 = 'value'; 86 | 87 | $this->responseMock->shouldReceive('withAddedHeader')->andReturn($this->returnValue); 88 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 89 | $this->assertEquals($this->responseMock->withAddedHeader($param, $param2), $sparkpostResponse->withAddedHeader($param, $param2)); 90 | } 91 | 92 | public function testWithoutHeader() 93 | { 94 | $param = 'header'; 95 | 96 | $this->responseMock->shouldReceive('withoutHeader')->andReturn($this->returnValue); 97 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 98 | $this->assertEquals($this->responseMock->withoutHeader($param), $sparkpostResponse->withoutHeader($param)); 99 | } 100 | 101 | public function testGetRequest() 102 | { 103 | $request = ['some' => 'request']; 104 | $this->responseMock->shouldReceive('getRequest')->andReturn($request); 105 | $sparkpostResponse = new SparkPostResponse($this->responseMock, $request); 106 | $this->assertEquals($sparkpostResponse->getRequest(), $request); 107 | } 108 | 109 | public function testWithBody() 110 | { 111 | $param = Mockery::mock('Psr\Http\Message\StreamInterface'); 112 | 113 | $this->responseMock->shouldReceive('withBody')->andReturn($this->returnValue); 114 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 115 | $this->assertEquals($this->responseMock->withBody($param), $sparkpostResponse->withBody($param)); 116 | } 117 | 118 | public function testGetStatusCode() 119 | { 120 | $this->responseMock->shouldReceive('getStatusCode')->andReturn($this->returnValue); 121 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 122 | $this->assertEquals($this->responseMock->getStatusCode(), $sparkpostResponse->getStatusCode()); 123 | } 124 | 125 | public function testWithStatus() 126 | { 127 | $param = 'status'; 128 | 129 | $this->responseMock->shouldReceive('withStatus')->andReturn($this->returnValue); 130 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 131 | $this->assertEquals($this->responseMock->withStatus($param), $sparkpostResponse->withStatus($param)); 132 | } 133 | 134 | public function testGetReasonPhrase() 135 | { 136 | $this->responseMock->shouldReceive('getReasonPhrase')->andReturn($this->returnValue); 137 | $sparkpostResponse = new SparkPostResponse($this->responseMock); 138 | $this->assertEquals($this->responseMock->getReasonPhrase(), $sparkpostResponse->getReasonPhrase()); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [Unreleased][unreleased] 6 | 7 | ## [2.3.0] - 2021-03-16 8 | - [#201](https://github.com/SparkPost/php-sparkpost/pull/201) Update examples, README 9 | - [#200](https://github.com/SparkPost/php-sparkpost/pull/200) PHP 8 support 10 | 11 | ## [2.2.1] - 2021-03-08 12 | - [#198](https://github.com/SparkPost/php-sparkpost/pull/198) Address #197: No longer need formfeed replacement. README work. 13 | - [#191](https://github.com/SparkPost/php-sparkpost/pull/191) Updating License 14 | 15 | ## [2.2.0] - 2019-06-04 16 | - [#187](https://github.com/SparkPost/php-sparkpost/pull/187) Updated composer.json 17 | - [#169](https://github.com/SparkPost/php-sparkpost/pull/169) Optional automatic retry on 5xx 18 | - [#166](https://github.com/SparkPost/php-sparkpost/pull/166/files) Quick fix for using the API without composer 19 | - [#149](https://github.com/SparkPost/php-sparkpost/pull/149) Setters should return current object 20 | 21 | ## [2.1.0] - 2017-01-09 22 | ### Added 23 | - [#161](https://github.com/SparkPost/php-sparkpost/pull/161) added example for sending email with attachment and simplified the examples setup 24 | - [#159](https://github.com/SparkPost/php-sparkpost/pull/159) added `debug` option for seeing the full request sent to SparkPost 25 | - [#151](https://github.com/SparkPost/php-sparkpost/pull/151) added packagist badges 26 | - [#139](https://github.com/SparkPost/php-sparkpost/pull/139) added examples for message events and templates 27 | 28 | ### Changed 29 | - [#150](https://github.com/SparkPost/php-sparkpost/issues/150) renamed the `Resource` class to `ResourceBase` since resource soft reserved in php 7 30 | - [#137](https://github.com/SparkPost/php-sparkpost/pull/137) cleaned up tests and post install scripts 31 | - [#138](https://github.com/SparkPost/php-sparkpost/pull/138) added PHP 7.1 as a test environment 32 | 33 | ### Fixed 34 | - [#156](https://github.com/SparkPost/php-sparkpost/pull/156) fixed typo in README.md 35 | - [#152](https://github.com/SparkPost/php-sparkpost/issues/152) fixed propagation of coverage tests to coveralls.io 36 | - [#147](https://github.com/SparkPost/php-sparkpost/pull/147) fixed examples in README.md 37 | - [#139](https://github.com/SparkPost/php-sparkpost/pull/139) fixed the ability to send using recipient lists 38 | - Issue[#141](https://github.com/SparkPost/php-sparkpost/issues/141) removed form feeds from the JSON body sent to the API 39 | 40 | ## [2.0.3] - 2016-07-28 41 | ### Fixed 42 | - Issue [#135](https://github.com/SparkPost/php-sparkpost/issues/135) reported `Http\Discovery\NotFoundException` caused by 2.0.2 update. 43 | 44 | ## [2.0.2] - 2016-07-28 45 | ### Fixed 46 | - [#131](https://github.com/SparkPost/php-sparkpost/pull/131) removed any dependency on Guzzle by replacing it with `MessageFactoryDiscovery` 47 | 48 | 49 | ## [2.0.1] - 2016-06-29 50 | ### Fixed 51 | - [#129](https://github.com/SparkPost/php-sparkpost/pull/129) issue with `content.from` being expected even when using a stored template 52 | 53 | ## [2.0.0] - 2016-06-24 54 | 55 | This major release included a complete refactor of the library to be a thin HTTP client layer with some sugar methods on the Transmission class. There is now a base resource that can be used to call any SparkPost API with a one to one mapping of payload parameters to what is listed in our API documentation. 56 | 57 | ### Changed 58 | - [#123](https://github.com/SparkPost/php-sparkpost/pull/123) Rewrote docs and updated composer name 59 | - [#122](https://github.com/SparkPost/php-sparkpost/pull/122) Add transmission class and examples 60 | - [#121](https://github.com/SparkPost/php-sparkpost/pull/121) Update base resource and tests 61 | 62 | ## [1.2.1] - 2016-05-27 63 | ### Fixed 64 | - [#111](https://github.com/SparkPost/php-sparkpost/pull/111) allow pass through of timeout setting in http config 65 | 66 | ## [1.2.0] - 2016-05-04 67 | ### Added 68 | - [EditorConfig](http://editorconfig.org/) file to maintain consistent coding style 69 | - `composer run-script fix-style` can now be run to enforce PSR-2 style 70 | 71 | ### Changed 72 | - Responses from the SparkPost API with HTTP status code 403 now properly raise with message, code, and description 73 | 74 | ### Fixed 75 | - Removed reliance on composer for version of library 76 | 77 | ## [1.1.0] - 2016-05-02 78 | ### Added 79 | - Message Events API added. 80 | 81 | ### Changed 82 | - Transmission API now accepts a DateTime object for startDate 83 | 84 | ## [1.0.3] - 2016-03-25 85 | ### Added 86 | - Support for attachments, inline attachments, inline css, sandbox, start time, and transactional options in `Transmission` class 87 | - API response exceptions now include message, code, and description from API 88 | 89 | ## [1.0.2] - 2016-02-28 90 | ### Fixed 91 | - Miscellaneous code cleanups related to docs and namespacing 92 | 93 | ## [1.0.1] - 2016-02-24 94 | ### Added 95 | - Example for using `setupUnwrapped()` to get a list of webhooks. 96 | - CHANGELOG.md for logging release updates and backfilled it with previous release. 97 | 98 | ### Fixed 99 | - Library will now throw a `SparkPost\APIReponseException` properly when a 4XX http status is encountered. 100 | 101 | ## 1.0.0 - 2015-10-15 102 | ### Added 103 | - Request adapter interface for passing in request adapters via `Ivory\HttpAdapter` 104 | - Ability to create 'unwrapped' modules for API endpoints that haven't had functionality included yet. 105 | - Instructions for setting up request adapters in README 106 | 107 | ### Changed 108 | - Library now requires PHP 5.5 or greater 109 | - Updated interface to be instance based with referenceable objects rather than static functions. 110 | 111 | ### Fixed 112 | - README now has proper code blocks denoting PHP language 113 | 114 | [unreleased]: https://github.com/sparkpost/php-sparkpost/compare/2.2.1...HEAD 115 | [2.2.1]: https://github.com/sparkpost/php-sparkpost/compare/2.2.0...2.2.1 116 | [2.2.0]: https://github.com/sparkpost/php-sparkpost/compare/2.1.0...2.2.0 117 | [2.1.0]: https://github.com/sparkpost/php-sparkpost/compare/2.0.3...2.1.0 118 | [2.0.3]: https://github.com/sparkpost/php-sparkpost/compare/2.0.2...2.0.3 119 | [2.0.2]: https://github.com/sparkpost/php-sparkpost/compare/2.0.1...2.0.2 120 | [2.0.1]: https://github.com/sparkpost/php-sparkpost/compare/2.0.0...2.0.1 121 | [2.0.0]: https://github.com/sparkpost/php-sparkpost/compare/1.2.1...2.0.0 122 | [1.2.1]: https://github.com/sparkpost/php-sparkpost/compare/1.2.0...1.2.1 123 | [1.2.0]: https://github.com/sparkpost/php-sparkpost/compare/v1.1.0...1.2.0 124 | [1.1.0]: https://github.com/sparkpost/php-sparkpost/compare/v1.0.3...v1.1.0 125 | [1.0.3]: https://github.com/sparkpost/php-sparkpost/compare/v1.0.2...v1.0.3 126 | [1.0.2]: https://github.com/sparkpost/php-sparkpost/compare/v1.0.1...v1.0.2 127 | [1.0.1]: https://github.com/sparkpost/php-sparkpost/compare/v1.0.0...v1.0.1 128 | -------------------------------------------------------------------------------- /lib/SparkPost/Transmission.php: -------------------------------------------------------------------------------- 1 | formatPayload($payload); 21 | } 22 | 23 | return parent::post($payload, $headers); 24 | } 25 | 26 | /** 27 | * Runs the given payload through the formatting functions. 28 | * 29 | * @param array $payload - the request body 30 | * 31 | * @return array - the modified request body 32 | */ 33 | public function formatPayload($payload) 34 | { 35 | $payload = $this->formatBlindCarbonCopy($payload); //Fixes BCCs into payload 36 | $payload = $this->formatCarbonCopy($payload); //Fixes CCs into payload 37 | $payload = $this->formatShorthandRecipients($payload); //Fixes shorthand recipients format 38 | 39 | return $payload; 40 | } 41 | 42 | /** 43 | * Formats bcc list into recipients list. 44 | * 45 | * @param array $payload - the request body 46 | * 47 | * @return array - the modified request body 48 | */ 49 | private function formatBlindCarbonCopy($payload) 50 | { 51 | 52 | //If there's a list of BCC recipients, move them into the correct format 53 | if (isset($payload['bcc'])) { 54 | $payload = $this->addListToRecipients($payload, 'bcc'); 55 | } 56 | 57 | return $payload; 58 | } 59 | 60 | /** 61 | * Formats cc list into recipients list and adds the CC header to the content. 62 | * 63 | * @param array $payload - the request body 64 | * 65 | * @return array - the modified request body 66 | */ 67 | private function formatCarbonCopy($payload) 68 | { 69 | if (isset($payload['cc'])) { 70 | $ccAddresses = []; 71 | for ($i = 0; $i < count($payload['cc']); ++$i) { 72 | array_push($ccAddresses, $this->toAddressString($payload['cc'][$i]['address'])); 73 | } 74 | 75 | // set up the content headers as either what it was before or an empty array 76 | $payload['content']['headers'] = isset($payload['content']['headers']) ? $payload['content']['headers'] : []; 77 | // add cc header 78 | $payload['content']['headers']['CC'] = implode(',', $ccAddresses); 79 | 80 | $payload = $this->addListToRecipients($payload, 'cc'); 81 | } 82 | 83 | return $payload; 84 | } 85 | 86 | /** 87 | * Formats all recipients into the long form of [ "name" => "John", "email" => "john@exmmple.com" ]. 88 | * 89 | * @param array $payload - the request body 90 | * 91 | * @return array - the modified request body 92 | */ 93 | private function formatShorthandRecipients($payload) 94 | { 95 | if (isset($payload['content']['from'])) { 96 | $payload['content']['from'] = $this->toAddressObject($payload['content']['from']); 97 | } 98 | 99 | for ($i = 0; $i < count($payload['recipients']); ++$i) { 100 | $payload['recipients'][$i]['address'] = $this->toAddressObject($payload['recipients'][$i]['address']); 101 | } 102 | 103 | return $payload; 104 | } 105 | 106 | /** 107 | * Loops through the given listName in the payload and adds all the recipients to the recipients list after removing their names. 108 | * 109 | * @param array $payload - the request body 110 | * @param array $listName - the name of the array in the payload to be moved to the recipients list 111 | * 112 | * @return array - the modified request body 113 | */ 114 | private function addListToRecipients($payload, $listName) 115 | { 116 | $originalAddress = $this->toAddressString($payload['recipients'][0]['address']); 117 | foreach ($payload[$listName] as $recipient) { 118 | $recipient['address'] = $this->toAddressObject($recipient['address']); 119 | $recipient['address']['header_to'] = $originalAddress; 120 | 121 | // remove name from address - name is only put in the header for cc and not at all for bcc 122 | if (isset($recipient['address']['name'])) { 123 | unset($recipient['address']['name']); 124 | } 125 | 126 | array_push($payload['recipients'], $recipient); 127 | } 128 | 129 | //Delete the original object from the payload. 130 | unset($payload[$listName]); 131 | 132 | return $payload; 133 | } 134 | 135 | /** 136 | * Takes the shorthand form of an email address and converts it to the long form. 137 | * 138 | * @param $address - the shorthand form of an email address "Name " 139 | * 140 | * @return array - the longhand form of an email address [ "name" => "John", "email" => "john@exmmple.com" ] 141 | */ 142 | private function toAddressObject($address) 143 | { 144 | $formatted = $address; 145 | if (is_string($formatted)) { 146 | $formatted = []; 147 | 148 | if ($this->isEmail($address)) { 149 | $formatted['email'] = $address; 150 | } elseif (preg_match('/"?(.[^"]*)?"?\s*<(.+)>/', $address, $matches)) { 151 | $name = trim($matches[1]); 152 | $formatted['name'] = $matches[1]; 153 | $formatted['email'] = $matches[2]; 154 | } else { 155 | throw new \Exception('Invalid address format: '.$address); 156 | } 157 | } 158 | 159 | return $formatted; 160 | } 161 | 162 | /** 163 | * Takes the longhand form of an email address and converts it to the shorthand form. 164 | * 165 | * @param $address - the longhand form of an email address [ "name" => "John", "email" => "john@exmmple.com" ] 166 | * @param string - the shorthand form of an email address "Name " 167 | */ 168 | private function toAddressString($address) 169 | { 170 | // convert object to string 171 | if (!is_string($address)) { 172 | if (isset($address['name'])) { 173 | $address = '"'.$address['name'].'" <'.$address['email'].'>'; 174 | } else { 175 | $address = $address['email']; 176 | } 177 | } 178 | 179 | return $address; 180 | } 181 | 182 | /** 183 | * Checks if a string is an email. 184 | * 185 | * @param string $email - a string that might be an email address 186 | * @param bool - true if the given string is an email 187 | */ 188 | private function isEmail($email) 189 | { 190 | if (filter_var($email, FILTER_VALIDATE_EMAIL)) { 191 | return true; 192 | } else { 193 | return false; 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /test/unit/TransmissionTest.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'from' => ['name' => 'Sparkpost Team', 'email' => 'postmaster@sendmailfor.me'], 18 | 'subject' => 'First Mailing From PHP', 19 | 'text' => 'Congratulations, {{name}}!! You just sent your very first mailing!', 20 | ], 21 | 'substitution_data' => ['name' => 'Avi'], 22 | 'recipients' => [ 23 | [ 24 | 'address' => [ 25 | 'name' => 'Vincent', 26 | 'email' => 'vincent.song@sparkpost.com', 27 | ], 28 | ], 29 | ['address' => 'test@example.com'], 30 | ], 31 | 'cc' => [ 32 | [ 33 | 'address' => [ 34 | 'email' => 'avi.goldman@sparkpost.com', 35 | ], 36 | ], 37 | ], 38 | 'bcc' => [ 39 | ['address' => 'Emely Giraldo '], 40 | ], 41 | 42 | ]; 43 | 44 | private $getTransmissionPayload = [ 45 | 'campaign_id' => 'thanksgiving', 46 | ]; 47 | 48 | /** 49 | * (non-PHPdoc). 50 | * 51 | * @before 52 | * 53 | * @see PHPUnit_Framework_TestCase::setUp() 54 | */ 55 | public function setUp(): void 56 | { 57 | //setup mock for the adapter 58 | $this->clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); 59 | 60 | $this->resource = new SparkPost($this->clientMock, ['key' => 'SPARKPOST_API_KEY', 'async' => false]); 61 | } 62 | 63 | public function tearDown(): void 64 | { 65 | Mockery::close(); 66 | } 67 | 68 | public function testInvalidEmailFormat() 69 | { 70 | $this->expectException(\Exception::class); 71 | 72 | $this->postTransmissionPayload['recipients'][] = [ 73 | 'address' => 'invalid email format', 74 | ]; 75 | 76 | $response = $this->resource->transmissions->post($this->postTransmissionPayload); 77 | } 78 | 79 | public function testGet() 80 | { 81 | $responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); 82 | $responseBodyMock = Mockery::mock(); 83 | 84 | $responseBody = ['results' => 'yay']; 85 | 86 | $this->clientMock->shouldReceive('sendRequest')-> 87 | once()-> 88 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 89 | andReturn($responseMock); 90 | 91 | $responseMock->shouldReceive('getStatusCode')->andReturn(200); 92 | $responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); 93 | $responseBodyMock->shouldReceive('__toString')->andReturn(json_encode($responseBody)); 94 | 95 | $response = $this->resource->transmissions->get($this->getTransmissionPayload); 96 | 97 | $this->assertEquals($responseBody, $response->getBody()); 98 | $this->assertEquals(200, $response->getStatusCode()); 99 | } 100 | 101 | public function testPut() 102 | { 103 | $responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); 104 | $responseBodyMock = Mockery::mock(); 105 | 106 | $responseBody = ['results' => 'yay']; 107 | 108 | $this->clientMock->shouldReceive('sendRequest')-> 109 | once()-> 110 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 111 | andReturn($responseMock); 112 | 113 | $responseMock->shouldReceive('getStatusCode')->andReturn(200); 114 | $responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); 115 | $responseBodyMock->shouldReceive('__toString')->andReturn(json_encode($responseBody)); 116 | 117 | $response = $this->resource->transmissions->put($this->getTransmissionPayload); 118 | 119 | $this->assertEquals($responseBody, $response->getBody()); 120 | $this->assertEquals(200, $response->getStatusCode()); 121 | } 122 | 123 | public function testPost() 124 | { 125 | $responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); 126 | $responseBodyMock = Mockery::mock(); 127 | 128 | $responseBody = ['results' => 'yay']; 129 | 130 | $this->clientMock->shouldReceive('sendRequest')-> 131 | once()-> 132 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 133 | andReturn($responseMock); 134 | 135 | $responseMock->shouldReceive('getStatusCode')->andReturn(200); 136 | $responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); 137 | $responseBodyMock->shouldReceive('__toString')->andReturn(json_encode($responseBody)); 138 | 139 | $response = $this->resource->transmissions->post($this->postTransmissionPayload); 140 | 141 | $this->assertEquals($responseBody, $response->getBody()); 142 | $this->assertEquals(200, $response->getStatusCode()); 143 | } 144 | 145 | public function testPostWithRecipientList() 146 | { 147 | $postTransmissionPayload = $this->postTransmissionPayload; 148 | $postTransmissionPayload['recipients'] = ['list_id' => 'SOME_LIST_ID']; 149 | 150 | $responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); 151 | $responseBodyMock = Mockery::mock(); 152 | 153 | $responseBody = ['results' => 'yay']; 154 | 155 | $this->clientMock->shouldReceive('sendRequest')-> 156 | once()-> 157 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 158 | andReturn($responseMock); 159 | 160 | $responseMock->shouldReceive('getStatusCode')->andReturn(200); 161 | $responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); 162 | $responseBodyMock->shouldReceive('__toString')->andReturn(json_encode($responseBody)); 163 | 164 | $response = $this->resource->transmissions->post(); 165 | 166 | $this->assertEquals($responseBody, $response->getBody()); 167 | $this->assertEquals(200, $response->getStatusCode()); 168 | } 169 | 170 | public function testDelete() 171 | { 172 | $responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); 173 | $responseBodyMock = Mockery::mock(); 174 | 175 | $responseBody = ['results' => 'yay']; 176 | 177 | $this->clientMock->shouldReceive('sendRequest')-> 178 | once()-> 179 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 180 | andReturn($responseMock); 181 | 182 | $responseMock->shouldReceive('getStatusCode')->andReturn(200); 183 | $responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); 184 | $responseBodyMock->shouldReceive('__toString')->andReturn(json_encode($responseBody)); 185 | 186 | $response = $this->resource->transmissions->delete($this->getTransmissionPayload); 187 | 188 | $this->assertEquals($responseBody, $response->getBody()); 189 | $this->assertEquals(200, $response->getStatusCode()); 190 | } 191 | 192 | public function testFormatPayload() 193 | { 194 | $correctFormattedPayload = json_decode('{"content":{"from":{"name":"Sparkpost Team","email":"postmaster@sendmailfor.me"},"subject":"First Mailing From PHP","text":"Congratulations, {{name}}!! You just sent your very first mailing!","headers":{"CC":"avi.goldman@sparkpost.com"}},"substitution_data":{"name":"Avi"},"recipients":[{"address":{"name":"Vincent","email":"vincent.song@sparkpost.com"}},{"address":{"email":"test@example.com"}},{"address":{"email":"emely.giraldo@sparkpost.com","header_to":"\"Vincent\" "}},{"address":{"email":"avi.goldman@sparkpost.com","header_to":"\"Vincent\" "}}]}', true); 195 | 196 | $formattedPayload = $this->resource->transmissions->formatPayload($this->postTransmissionPayload); 197 | $this->assertEquals($correctFormattedPayload, $formattedPayload); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /lib/SparkPost/SparkPost.php: -------------------------------------------------------------------------------- 1 | 'api.sparkpost.com', 38 | 'protocol' => 'https', 39 | 'port' => 443, 40 | 'key' => '', 41 | 'version' => 'v1', 42 | 'async' => true, 43 | 'debug' => false, 44 | 'retries' => 0 45 | ]; 46 | 47 | /** 48 | * @var Transmission Instance of Transmission class 49 | */ 50 | public $transmissions; 51 | 52 | /** 53 | * Sets up the SparkPost instance. 54 | * 55 | * @param HttpClient $httpClient - An httplug client or adapter 56 | * @param array $options - An array to overide default options or a string to be used as an API key 57 | */ 58 | public function __construct($httpClient, array $options) 59 | { 60 | $this->setOptions($options); 61 | $this->setHttpClient($httpClient); 62 | $this->setupEndpoints(); 63 | } 64 | 65 | /** 66 | * Sends either sync or async request based on async option. 67 | * 68 | * @param string $method 69 | * @param string $uri 70 | * @param array $payload - either used as the request body or url query params 71 | * @param array $headers 72 | * 73 | * @return SparkPostPromise|SparkPostResponse Promise or Response depending on sync or async request 74 | */ 75 | public function request($method = 'GET', $uri = '', $payload = [], $headers = []) 76 | { 77 | if ($this->options['async'] === true) { 78 | return $this->asyncRequest($method, $uri, $payload, $headers); 79 | } else { 80 | return $this->syncRequest($method, $uri, $payload, $headers); 81 | } 82 | } 83 | 84 | /** 85 | * Sends sync request to SparkPost API. 86 | * 87 | * @param string $method 88 | * @param string $uri 89 | * @param array $payload 90 | * @param array $headers 91 | * 92 | * @return SparkPostResponse 93 | * 94 | * @throws SparkPostException 95 | */ 96 | public function syncRequest($method = 'GET', $uri = '', $payload = [], $headers = []) 97 | { 98 | $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); 99 | $request = call_user_func_array(array($this, 'buildRequestInstance'), $requestValues); 100 | 101 | $retries = $this->options['retries']; 102 | try { 103 | if ($retries > 0) { 104 | $resp = $this->syncReqWithRetry($request, $retries); 105 | } else { 106 | $resp = $this->httpClient->sendRequest($request); 107 | } 108 | return new SparkPostResponse($resp, $this->ifDebug($requestValues)); 109 | } catch (\Exception $exception) { 110 | throw new SparkPostException($exception, $this->ifDebug($requestValues)); 111 | } 112 | } 113 | 114 | private function syncReqWithRetry($request, $retries) 115 | { 116 | $resp = $this->httpClient->sendRequest($request); 117 | $status = $resp->getStatusCode(); 118 | if ($status >= 500 && $status <= 599 && $retries > 0) { 119 | return $this->syncReqWithRetry($request, $retries-1); 120 | } 121 | return $resp; 122 | } 123 | 124 | /** 125 | * Sends async request to SparkPost API. 126 | * 127 | * @param string $method 128 | * @param string $uri 129 | * @param array $payload 130 | * @param array $headers 131 | * 132 | * @return SparkPostPromise 133 | */ 134 | public function asyncRequest($method = 'GET', $uri = '', $payload = [], $headers = []) 135 | { 136 | if ($this->httpClient instanceof HttpAsyncClient) { 137 | $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); 138 | $request = call_user_func_array(array($this, 'buildRequestInstance'), $requestValues); 139 | 140 | $retries = $this->options['retries']; 141 | if ($retries > 0) { 142 | return new SparkPostPromise($this->asyncReqWithRetry($request, $retries), $this->ifDebug($requestValues)); 143 | } else { 144 | return new SparkPostPromise($this->httpClient->sendAsyncRequest($request), $this->ifDebug($requestValues)); 145 | } 146 | } else { 147 | throw new \Exception('Your http client does not support asynchronous requests. Please use a different client or use synchronous requests.'); 148 | } 149 | } 150 | 151 | private function asyncReqWithRetry($request, $retries) 152 | { 153 | return $this->httpClient->sendAsyncRequest($request)->then(function($response) use ($request, $retries) { 154 | $status = $response->getStatusCode(); 155 | if ($status >= 500 && $status <= 599 && $retries > 0) { 156 | return $this->asyncReqWithRetry($request, $retries-1); 157 | } 158 | return $response; 159 | }); 160 | } 161 | 162 | /** 163 | * Builds request values from given params. 164 | * 165 | * @param string $method 166 | * @param string $uri 167 | * @param array $payload 168 | * @param array $headers 169 | * 170 | * @return array $requestValues 171 | */ 172 | public function buildRequestValues($method, $uri, $payload, $headers) 173 | { 174 | $method = trim(strtoupper($method)); 175 | 176 | if ($method === 'GET') { 177 | $params = $payload; 178 | $body = []; 179 | } else { 180 | $params = []; 181 | $body = $payload; 182 | } 183 | 184 | $url = $this->getUrl($uri, $params); 185 | $headers = $this->getHttpHeaders($headers); 186 | 187 | // old form-feed workaround now removed 188 | $body = json_encode($body); 189 | return [ 190 | 'method' => $method, 191 | 'url' => $url, 192 | 'headers' => $headers, 193 | 'body' => $body, 194 | ]; 195 | } 196 | 197 | /** 198 | * Build RequestInterface from given params. 199 | * 200 | * @param array $requestValues 201 | * 202 | * @return RequestInterface 203 | */ 204 | public function buildRequestInstance($method, $url, $headers, $body) 205 | { 206 | return $this->getMessageFactory()->createRequest($method, $url, $headers, $body); 207 | } 208 | 209 | /** 210 | * Build RequestInterface from given params. 211 | * 212 | * @param array $requestValues 213 | * 214 | * @return RequestInterface 215 | */ 216 | public function buildRequest($method, $uri, $payload, $headers) 217 | { 218 | $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); 219 | return call_user_func_array(array($this, 'buildRequestInstance'), $requestValues); 220 | } 221 | 222 | /** 223 | * Returns an array for the request headers. 224 | * 225 | * @param array $headers - any custom headers for the request 226 | * 227 | * @return array $headers - headers for the request 228 | */ 229 | public function getHttpHeaders($headers = []) 230 | { 231 | $constantHeaders = [ 232 | 'Authorization' => $this->options['key'], 233 | 'Content-Type' => 'application/json', 234 | 'User-Agent' => 'php-sparkpost/'.$this->version, 235 | ]; 236 | 237 | foreach ($constantHeaders as $key => $value) { 238 | $headers[$key] = $value; 239 | } 240 | 241 | return $headers; 242 | } 243 | 244 | /** 245 | * Builds the request url from the options and given params. 246 | * 247 | * @param string $path - the path in the url to hit 248 | * @param array $params - query parameters to be encoded into the url 249 | * 250 | * @return string $url - the url to send the desired request to 251 | */ 252 | public function getUrl($path, $params = []) 253 | { 254 | $options = $this->options; 255 | 256 | $paramsArray = []; 257 | foreach ($params as $key => $value) { 258 | if (is_array($value)) { 259 | $value = implode(',', $value); 260 | } 261 | 262 | array_push($paramsArray, $key.'='.$value); 263 | } 264 | 265 | $paramsString = implode('&', $paramsArray); 266 | 267 | return $options['protocol'].'://'.$options['host'].($options['port'] ? ':'.$options['port'] : '').'/api/'.$options['version'].'/'.$path.($paramsString ? '?'.$paramsString : ''); 268 | } 269 | 270 | /** 271 | * Sets $httpClient to be used for request. 272 | * 273 | * @param HttpClient|HttpAsyncClient $httpClient - the client to be used for request 274 | * 275 | * @return SparkPost 276 | */ 277 | public function setHttpClient($httpClient) 278 | { 279 | if (!($httpClient instanceof HttpAsyncClient || $httpClient instanceof HttpClient)) { 280 | throw new \LogicException(sprintf('Parameter to SparkPost::setHttpClient must be instance of "%s" or "%s"', HttpClient::class, HttpAsyncClient::class)); 281 | } 282 | 283 | $this->httpClient = $httpClient; 284 | 285 | return $this; 286 | } 287 | 288 | /** 289 | * Sets the options from the param and defaults for the SparkPost object. 290 | * 291 | * @param array $options - either an string API key or an array of options 292 | * 293 | * @return SparkPost 294 | */ 295 | public function setOptions($options) 296 | { 297 | // if the options map is a string we should assume that its an api key 298 | if (is_string($options)) { 299 | $options = ['key' => $options]; 300 | } 301 | 302 | // Validate API key because its required 303 | if (!isset($this->options['key']) && (!isset($options['key']) || !preg_match('/\S/', $options['key']))) { 304 | throw new \Exception('You must provide an API key'); 305 | } 306 | 307 | $this->options = isset($this->options) ? $this->options : self::$defaultOptions; 308 | 309 | // set options, overriding defaults 310 | foreach ($options as $option => $value) { 311 | if (key_exists($option, $this->options)) { 312 | $this->options[$option] = $value; 313 | } 314 | } 315 | 316 | return $this; 317 | } 318 | 319 | /** 320 | * Returns the given value if debugging, an empty instance otherwise. 321 | * 322 | * @param any $param 323 | * 324 | * @return any $param 325 | */ 326 | private function ifDebug($param) 327 | { 328 | return $this->options['debug'] ? $param : null; 329 | } 330 | 331 | /** 332 | * Sets up any endpoints to custom classes e.g. $this->transmissions. 333 | */ 334 | private function setupEndpoints() 335 | { 336 | $this->transmissions = new Transmission($this); 337 | } 338 | 339 | /** 340 | * @return RequestFactory 341 | */ 342 | private function getMessageFactory() 343 | { 344 | if (!$this->messageFactory) { 345 | $this->messageFactory = MessageFactoryDiscovery::find(); 346 | } 347 | 348 | return $this->messageFactory; 349 | } 350 | 351 | /** 352 | * @param RequestFactory $messageFactory 353 | * 354 | * @return SparkPost 355 | */ 356 | public function setMessageFactory(RequestFactory $messageFactory) 357 | { 358 | $this->messageFactory = $messageFactory; 359 | 360 | return $this; 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Sign up](https://app.sparkpost.com/join?plan=free-0817?src=Social%20Media&sfdcid=70160000000pqBb&pc=GitHubSignUp&utm_source=github&utm_medium=social-media&utm_campaign=github&utm_content=sign-up) for a SparkPost account and visit our [Developer Hub](https://developers.sparkpost.com) for even more content. 4 | 5 | # SparkPost PHP Library 6 | 7 | [![Travis CI](https://travis-ci.org/SparkPost/php-sparkpost.svg?branch=master)](https://travis-ci.org/SparkPost/php-sparkpost) 8 | [![Coverage Status](https://coveralls.io/repos/SparkPost/php-sparkpost/badge.svg?branch=master&service=github)](https://coveralls.io/github/SparkPost/php-sparkpost?branch=master) 9 | [![Downloads](https://img.shields.io/packagist/dt/sparkpost/sparkpost.svg?maxAge=3600)](https://packagist.org/packages/sparkpost/sparkpost) 10 | [![Packagist](https://img.shields.io/packagist/v/sparkpost/sparkpost.svg?maxAge=3600)](https://packagist.org/packages/sparkpost/sparkpost) 11 | 12 | The official PHP library for using [the SparkPost REST API](https://developers.sparkpost.com/api/). 13 | 14 | Before using this library, you must have a valid API Key. To get an API Key, please log in to your SparkPost account and generate one in the Settings page. 15 | 16 | ## Installation 17 | **Please note: The composer package `sparkpost/php-sparkpost` has been changed to `sparkpost/sparkpost` starting with version 2.0.** 18 | 19 | The recommended way to install the SparkPost PHP Library is through composer. 20 | 21 | ``` 22 | # Install Composer 23 | curl -sS https://getcomposer.org/installer | php 24 | ``` 25 | 26 | Sparkpost requires php-http client (see [Setting up a Request Adapter](#setting-up-a-request-adapter)). There are several [providers](https://packagist.org/providers/php-http/client-implementation) available. If you were using guzzle6 your install might look like this. 27 | 28 | ``` 29 | composer require php-http/guzzle6-adapter "^1.1" 30 | composer require guzzlehttp/guzzle "^6.0" 31 | ``` 32 | 33 | Next, run the Composer command to install the SparkPost PHP Library: 34 | 35 | ``` 36 | composer require sparkpost/sparkpost 37 | ``` 38 | 39 | After installing, you need to require Composer's autoloader: 40 | 41 | ```php 42 | require 'vendor/autoload.php'; 43 | use SparkPost\SparkPost; 44 | ``` 45 | 46 | **Note:** Without composer the costs outweigh the benefits of using the PHP client library. A simple function like the one in [issue #164](https://github.com/SparkPost/php-sparkpost/issues/164#issuecomment-289888237) wraps the SparkPost API and makes it easy to use the API without resolving the composer dependencies. 47 | 48 | ## Running with IDEs 49 | 50 | When running with `xdebug` under an IDE such as VS Code, you may see an exception is thrown in file `vendor/php-http/discovery/src/Strategy/PuliBetaStrategy.php`: 51 | 52 | ``` 53 | Exception has occurred. 54 | Http\Discovery\Exception\PuliUnavailableException: Puli Factory is not available 55 | ``` 56 | 57 | [This is usual](http://docs.php-http.org/en/latest/discovery.html#puli-factory-is-not-available). Puli is not required to use the library. You can resume running after the exception. 58 | 59 | You can prevent the exception, by setting the discovery strategies, prior to creating the adapter object: 60 | ```php 61 | // Prevent annoying "Puli exception" during work with xdebug / IDE 62 | // See https://github.com/getsentry/sentry-php/issues/801 63 | \Http\Discovery\ClassDiscovery::setStrategies([ 64 | // \Http\Discovery\Strategy\PuliBetaStrategy::class, // Deliberately disabled 65 | \Http\Discovery\Strategy\CommonClassesStrategy::class, 66 | \Http\Discovery\Strategy\CommonPsr17ClassesStrategy::class, 67 | ]); 68 | ``` 69 | 70 | ## Setting up a Request Adapter 71 | 72 | Because of dependency collision, we have opted to use a request adapter rather than 73 | requiring a request library. This means that your application will need to pass in 74 | a request adapter to the constructor of the SparkPost Library. We use the [HTTPlug](https://github.com/php-http/httplug) in SparkPost. Please visit their repo for a list of supported [clients and adapters](http://docs.php-http.org/en/latest/clients.html). If you don't currently use a request library, you will 75 | need to require one and create a client from it and pass it along. The example below uses the GuzzleHttp Client Library. 76 | 77 | A Client can be setup like so: 78 | 79 | ```php 80 | 'YOUR_API_KEY']); 89 | ?> 90 | ``` 91 | 92 | ## Initialization 93 | #### new Sparkpost(httpClient, options) 94 | * `httpClient` 95 | * Required: Yes 96 | * HTTP client or adapter supported by HTTPlug 97 | * `options` 98 | * Required: Yes 99 | * Type: `String` or `Array` 100 | * A valid Sparkpost API key or an array of options 101 | * `options.key` 102 | * Required: Yes 103 | * Type: `String` 104 | * A valid Sparkpost API key 105 | * `options.host` 106 | * Required: No 107 | * Type: `String` 108 | * Default: `api.sparkpost.com` 109 | * `options.protocol` 110 | * Required: No 111 | * Type: `String` 112 | * Default: `https` 113 | * `options.port` 114 | * Required: No 115 | * Type: `Number` 116 | * Default: 443 117 | * `options.version` 118 | * Required: No 119 | * Type: `String` 120 | * Default: `v1` 121 | * `options.async` 122 | * Required: No 123 | * Type: `Boolean` 124 | * Default: `true` 125 | * `async` defines if the `request` function sends an asynchronous or synchronous request. If your client does not support async requests set this to `false` 126 | * `options.retries` 127 | * Required: No 128 | * Type: `Number` 129 | * Default: `0` 130 | * `retries` controls how many API call attempts the client makes after receiving a 5xx response 131 | * `options.debug` 132 | * Required: No 133 | * Type: `Boolean` 134 | * Default: `false` 135 | * If `debug` is true, then all `SparkPostResponse` and `SparkPostException` instances will return any array of the request values through the function `getRequest` 136 | 137 | ## Methods 138 | ### request(method, uri [, payload [, headers]]) 139 | * `method` 140 | * Required: Yes 141 | * Type: `String` 142 | * HTTP method for request 143 | * `uri` 144 | * Required: Yes 145 | * Type: `String` 146 | * The URI to receive the request 147 | * `payload` 148 | * Required: No 149 | * Type: `Array` 150 | * If the method is `GET` the values are encoded into the URL. Otherwise, if the method is `POST`, `PUT`, or `DELETE` the payload is used for the request body. 151 | * `headers` 152 | * Required: No 153 | * Type: `Array` 154 | * Custom headers to be sent with the request. 155 | 156 | ### syncRequest(method, uri [, payload [, headers]]) 157 | Sends a synchronous request to the SparkPost API and returns a `SparkPostResponse` 158 | 159 | ### asyncRequest(method, uri [, payload [, headers]]) 160 | Sends an asynchronous request to the SparkPost API and returns a `SparkPostPromise` 161 | 162 | ### setHttpClient(httpClient) 163 | * `httpClient` 164 | * Required: Yes 165 | * HTTP client or adapter supported by HTTPlug 166 | 167 | ### setOptions(options) 168 | * `options` 169 | * Required: Yes 170 | * Type: `Array` 171 | * See constructor 172 | 173 | ## Endpoints 174 | ### transmissions 175 | * **post(payload)** 176 | * `payload` - see request options 177 | * `payload.cc` 178 | * Required: No 179 | * Type: `Array` 180 | * Recipients to receive a carbon copy of the transmission 181 | * `payload.bcc` 182 | * Required: No 183 | * Type: `Array` 184 | * Recipients to discreetly receive a carbon copy of the transmission 185 | 186 | ## Examples 187 | 188 | ### Send An Email Using The Transmissions Endpoint 189 | ```php 190 | getenv('SPARKPOST_API_KEY'), 'async' => false]); 201 | 202 | try { 203 | $response = $sparky->transmissions->post([ 204 | 'content' => [ 205 | 'from' => [ 206 | 'name' => 'SparkPost Team', 207 | 'email' => 'from@sparkpostbox.com', 208 | ], 209 | 'subject' => 'First Mailing From PHP', 210 | 'html' => '

Congratulations, {{name}}!

You just sent your very first mailing!

', 211 | 'text' => 'Congratulations, {{name}}!! You just sent your very first mailing!', 212 | ], 213 | 'substitution_data' => ['name' => 'YOUR_FIRST_NAME'], 214 | 'recipients' => [ 215 | [ 216 | 'address' => [ 217 | 'name' => 'YOUR_NAME', 218 | 'email' => 'YOUR_EMAIL', 219 | ], 220 | ], 221 | ], 222 | 'cc' => [ 223 | [ 224 | 'address' => [ 225 | 'name' => 'ANOTHER_NAME', 226 | 'email' => 'ANOTHER_EMAIL', 227 | ], 228 | ], 229 | ], 230 | 'bcc' => [ 231 | [ 232 | 'address' => [ 233 | 'name' => 'AND_ANOTHER_NAME', 234 | 'email' => 'AND_ANOTHER_EMAIL', 235 | ], 236 | ], 237 | ], 238 | ]); 239 | } catch (\Exception $error) { 240 | var_dump($error); 241 | } 242 | print($response->getStatusCode()); 243 | $results = $response->getBody()['results']; 244 | var_dump($results); 245 | ?> 246 | ``` 247 | 248 | More examples [here](./examples/): 249 | ### [Transmissions](./examples/transmissions/) 250 | - Create with attachment 251 | - Create with recipient list 252 | - Create with cc and bcc 253 | - Create with template 254 | - Create 255 | - Delete (scheduled transmission by campaign_id *only*) 256 | 257 | ### [Templates](./examples/templates/) 258 | - Create 259 | - Get 260 | - Get (list) all 261 | - Update 262 | - Delete 263 | 264 | ### [Message Events](./examples/message-events/) 265 | - get 266 | - get (with retry logic) 267 | 268 | ### Send An API Call Using The Base Request Function 269 | 270 | We provide a base request function to access any of our API resources. 271 | ```php 272 | getenv('SPARKPOST_API_KEY'), 282 | 'async' => false]); 283 | 284 | $webhookId = 'afd20f50-865a-11eb-ac38-6d7965d56459'; 285 | $response = $sparky->request('DELETE', 'webhooks/' . $webhookId); 286 | print($response->getStatusCode()); 287 | ?> 288 | ``` 289 | 290 | > Be sure to not have a leading `/` in your resource URI. 291 | 292 | For complete list of resources, refer to [API documentation](https://developers.sparkpost.com/api/). 293 | 294 | ## Handling Responses 295 | The API calls either return a `SparkPostPromise` or `SparkPostResponse` depending on if `async` is `true` or `false` 296 | 297 | ### Synchronous 298 | ```php 299 | $sparky->setOptions(['async' => false]); 300 | try { 301 | $response = ... // YOUR API CALL GOES HERE 302 | 303 | echo $response->getStatusCode()."\n"; 304 | print_r($response->getBody())."\n"; 305 | } 306 | catch (\Exception $e) { 307 | echo $e->getCode()."\n"; 308 | echo $e->getMessage()."\n"; 309 | } 310 | ``` 311 | 312 | ### Asynchronous 313 | Asynchronous an be handled in two ways: by passing callbacks or waiting for the promise to be fulfilled. Waiting acts like synchronous request. 314 | ##### Wait (Synchronous) 315 | ```php 316 | 317 | $promise = ... // YOUR API CALL GOES HERE 318 | 319 | try { 320 | $response = $promise->wait(); 321 | echo $response->getStatusCode()."\n"; 322 | print_r($response->getBody())."\n"; 323 | } catch (\Exception $e) { 324 | echo $e->getCode()."\n"; 325 | echo $e->getMessage()."\n"; 326 | } 327 | 328 | echo "I will print out after the promise is fulfilled"; 329 | ``` 330 | 331 | ##### Then (Asynchronous) 332 | ```php 333 | $promise = ... // YOUR API CALL GOES HERE 334 | 335 | $promise->then( 336 | // Success callback 337 | function ($response) { 338 | echo $response->getStatusCode()."\n"; 339 | print_r($response->getBody())."\n"; 340 | }, 341 | // Failure callback 342 | function (Exception $e) { 343 | echo $e->getCode()."\n"; 344 | echo $e->getMessage()."\n"; 345 | } 346 | ); 347 | 348 | echo "I will print out before the promise is fulfilled"; 349 | 350 | // You can combine multiple promises using \GuzzleHttp\Promise\all() and other functions from the library. 351 | $promise->wait(); 352 | ``` 353 | 354 | ## Handling Exceptions 355 | An exception will be thrown in two cases: there is a problem with the request or the server returns a status code of `400` or higher. 356 | 357 | ### SparkPostException 358 | * **getCode()** 359 | * Returns the response status code of `400` or higher 360 | * **getMessage()** 361 | * Returns the exception message 362 | * **getBody()** 363 | * If there is a response body it returns it as an `Array`. Otherwise it returns `null`. 364 | * **getRequest()** 365 | * Returns an array with the request values `method`, `url`, `headers`, `body` when `debug` is `true` 366 | 367 | 368 | ### Contributing 369 | See [contributing](https://github.com/SparkPost/php-sparkpost/blob/master/CONTRIBUTING.md). 370 | -------------------------------------------------------------------------------- /test/unit/SparkPostTest.php: -------------------------------------------------------------------------------- 1 | [ 33 | 'from' => ['name' => 'Sparkpost Team', 'email' => 'postmaster@sendmailfor.me'], 34 | 'subject' => 'First Mailing From PHP', 35 | 'text' => 'Congratulations, {{name}}!! You just sent your very first mailing!', 36 | ], 37 | 'substitution_data' => ['name' => 'Avi'], 38 | 'recipients' => [ 39 | ['address' => 'avi.goldman@sparkpost.com'], 40 | ], 41 | ]; 42 | 43 | private $getTransmissionPayload = [ 44 | 'campaign_id' => 'thanksgiving', 45 | ]; 46 | 47 | public function setUp(): void 48 | { 49 | // response mock up 50 | $responseBodyMock = Mockery::mock(); 51 | $this->responseBody = ['results' => 'yay']; 52 | $this->responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); 53 | $this->responseMock->shouldReceive('getStatusCode')->andReturn(200); 54 | $this->responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); 55 | $responseBodyMock->shouldReceive('__toString')->andReturn(json_encode($this->responseBody)); 56 | 57 | $errorBodyMock = Mockery::mock(); 58 | $this->badResponseBody = ['errors' => []]; 59 | $this->badResponseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); 60 | $this->badResponseMock->shouldReceive('getStatusCode')->andReturn(503); 61 | $this->badResponseMock->shouldReceive('getBody')->andReturn($errorBodyMock); 62 | $errorBodyMock->shouldReceive('__toString')->andReturn(json_encode($this->badResponseBody)); 63 | 64 | // exception mock up 65 | $exceptionResponseMock = Mockery::mock(); 66 | $this->exceptionBody = ['results' => 'failed']; 67 | $this->exceptionMock = Mockery::mock('Http\Client\Exception\HttpException'); 68 | $this->exceptionMock->shouldReceive('getResponse')->andReturn($exceptionResponseMock); 69 | $exceptionResponseMock->shouldReceive('getStatusCode')->andReturn(500); 70 | $exceptionResponseMock->shouldReceive('getBody->__toString')->andReturn(json_encode($this->exceptionBody)); 71 | 72 | // promise mock up 73 | $this->promiseMock = Mockery::mock('Http\Promise\Promise'); 74 | 75 | //setup mock for the adapter 76 | $this->clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); 77 | $this->clientMock->shouldReceive('sendAsyncRequest')-> 78 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 79 | andReturn($this->promiseMock); 80 | 81 | $this->resource = new SparkPost($this->clientMock, ['key' => 'SPARKPOST_API_KEY']); 82 | } 83 | 84 | public function tearDown(): void 85 | { 86 | Mockery::close(); 87 | } 88 | 89 | public function testRequestSync() 90 | { 91 | $this->resource->setOptions(['async' => false]); 92 | $this->clientMock->shouldReceive('sendRequest')->andReturn($this->responseMock); 93 | 94 | $this->assertInstanceOf('SparkPost\SparkPostResponse', $this->resource->request('POST', 'transmissions', $this->postTransmissionPayload)); 95 | } 96 | 97 | public function testRequestAsync() 98 | { 99 | $promiseMock = Mockery::mock('Http\Promise\Promise'); 100 | $this->resource->setOptions(['async' => true]); 101 | $this->clientMock->shouldReceive('sendAsyncRequest')->andReturn($promiseMock); 102 | 103 | $this->assertInstanceOf('SparkPost\SparkPostPromise', $this->resource->request('GET', 'transmissions', $this->getTransmissionPayload)); 104 | } 105 | 106 | public function testDebugOptionWhenFalse() { 107 | $this->resource->setOptions(['async' => false, 'debug' => false]); 108 | $this->clientMock->shouldReceive('sendRequest')->andReturn($this->responseMock); 109 | 110 | $response = $this->resource->request('POST', 'transmissions', $this->postTransmissionPayload); 111 | 112 | $this->assertEquals($response->getRequest(), null); 113 | } 114 | 115 | public function testDebugOptionWhenTrue() { 116 | // setup 117 | $this->resource->setOptions(['async' => false, 'debug' => true]); 118 | 119 | // successful 120 | $this->clientMock->shouldReceive('sendRequest')->once()->andReturn($this->responseMock); 121 | $response = $this->resource->request('POST', 'transmissions', $this->postTransmissionPayload); 122 | $this->assertEquals(json_decode($response->getRequest()['body'], true), $this->postTransmissionPayload); 123 | 124 | // unsuccessful 125 | $this->clientMock->shouldReceive('sendRequest')->once()->andThrow($this->exceptionMock); 126 | 127 | try { 128 | $response = $this->resource->request('POST', 'transmissions', $this->postTransmissionPayload); 129 | } 130 | catch (\Exception $e) { 131 | $this->assertEquals(json_decode($e->getRequest()['body'], true), $this->postTransmissionPayload); 132 | } 133 | } 134 | 135 | public function testSuccessfulSyncRequest() 136 | { 137 | $this->clientMock->shouldReceive('sendRequest')-> 138 | once()-> 139 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 140 | andReturn($this->responseMock); 141 | 142 | $response = $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); 143 | 144 | $this->assertEquals($this->responseBody, $response->getBody()); 145 | $this->assertEquals(200, $response->getStatusCode()); 146 | } 147 | 148 | public function testUnsuccessfulSyncRequest() 149 | { 150 | $this->clientMock->shouldReceive('sendRequest')-> 151 | once()-> 152 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 153 | andThrow($this->exceptionMock); 154 | 155 | try { 156 | $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); 157 | } catch (\Exception $e) { 158 | $this->assertEquals($this->exceptionBody, $e->getBody()); 159 | $this->assertEquals(500, $e->getCode()); 160 | } 161 | } 162 | 163 | public function testSuccessfulSyncRequestWithRetries() 164 | { 165 | $this->clientMock->shouldReceive('sendRequest')-> 166 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 167 | andReturn($this->badResponseMock, $this->badResponseMock, $this->responseMock); 168 | 169 | $this->resource->setOptions(['retries' => 2]); 170 | $response = $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); 171 | 172 | $this->assertEquals($this->responseBody, $response->getBody()); 173 | $this->assertEquals(200, $response->getStatusCode()); 174 | } 175 | 176 | public function testUnsuccessfulSyncRequestWithRetries() 177 | { 178 | $this->clientMock->shouldReceive('sendRequest')-> 179 | once()-> 180 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 181 | andThrow($this->exceptionMock); 182 | 183 | $this->resource->setOptions(['retries' => 2]); 184 | try { 185 | $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); 186 | } catch (\Exception $e) { 187 | $this->assertEquals($this->exceptionBody, $e->getBody()); 188 | $this->assertEquals(500, $e->getCode()); 189 | } 190 | } 191 | 192 | public function testSuccessfulAsyncRequestWithWait() 193 | { 194 | $this->promiseMock->shouldReceive('wait')->andReturn($this->responseMock); 195 | 196 | $promise = $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); 197 | $response = $promise->wait(); 198 | 199 | $this->assertEquals($this->responseBody, $response->getBody()); 200 | $this->assertEquals(200, $response->getStatusCode()); 201 | } 202 | 203 | public function testUnsuccessfulAsyncRequestWithWait() 204 | { 205 | $this->promiseMock->shouldReceive('wait')->andThrow($this->exceptionMock); 206 | 207 | $promise = $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); 208 | 209 | try { 210 | $response = $promise->wait(); 211 | } catch (\Exception $e) { 212 | $this->assertEquals($this->exceptionBody, $e->getBody()); 213 | $this->assertEquals(500, $e->getCode()); 214 | } 215 | } 216 | 217 | public function testSuccessfulAsyncRequestWithThen() 218 | { 219 | $guzzlePromise = new GuzzleFulfilledPromise($this->responseMock); 220 | $result = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); 221 | 222 | $promise = new SparkPostPromise(new GuzzleAdapterPromise($guzzlePromise, $result)); 223 | 224 | $responseBody = $this->responseBody; 225 | $promise->then(function ($response) use ($responseBody) { 226 | $this->assertEquals(200, $response->getStatusCode()); 227 | $this->assertEquals($responseBody, $response->getBody()); 228 | }, null)->wait(); 229 | } 230 | 231 | public function testUnsuccessfulAsyncRequestWithThen() 232 | { 233 | $guzzlePromise = new GuzzleRejectedPromise($this->exceptionMock); 234 | $result = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); 235 | 236 | $promise = new SparkPostPromise(new GuzzleAdapterPromise($guzzlePromise, $result)); 237 | 238 | $exceptionBody = $this->exceptionBody; 239 | $promise->then(null, function ($exception) use ($exceptionBody) { 240 | $this->assertEquals(500, $exception->getCode()); 241 | $this->assertEquals($exceptionBody, $exception->getBody()); 242 | })->wait(); 243 | } 244 | 245 | public function testSuccessfulAsyncRequestWithRetries() 246 | { 247 | $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); 248 | $clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); 249 | $clientMock->shouldReceive('sendAsyncRequest')-> 250 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 251 | andReturn( 252 | new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->badResponseMock), $testReq), 253 | new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->badResponseMock), $testReq), 254 | new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->responseMock), $testReq) 255 | ); 256 | 257 | $resource = new SparkPost($clientMock, ['key' => 'SPARKPOST_API_KEY']); 258 | 259 | $resource->setOptions(['async' => true, 'retries' => 2]); 260 | $promise = $resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); 261 | $promise->then(function($resp) { 262 | $this->assertEquals(200, $resp->getStatusCode()); 263 | })->wait(); 264 | } 265 | 266 | public function testUnsuccessfulAsyncRequestWithRetries() 267 | { 268 | $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); 269 | $rejectedPromise = new GuzzleRejectedPromise($this->exceptionMock); 270 | $clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); 271 | $clientMock->shouldReceive('sendAsyncRequest')-> 272 | with(Mockery::type('GuzzleHttp\Psr7\Request'))-> 273 | andReturn(new GuzzleAdapterPromise($rejectedPromise, $testReq)); 274 | 275 | $resource = new SparkPost($clientMock, ['key' => 'SPARKPOST_API_KEY']); 276 | 277 | $resource->setOptions(['async' => true, 'retries' => 2]); 278 | $promise = $resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); 279 | $promise->then(null, function($exception) { 280 | $this->assertEquals(500, $exception->getCode()); 281 | $this->assertEquals($this->exceptionBody, $exception->getBody()); 282 | })->wait(); 283 | } 284 | 285 | public function testPromise() 286 | { 287 | $promise = $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); 288 | 289 | $this->promiseMock->shouldReceive('getState')->twice()->andReturn('pending'); 290 | $this->assertEquals($this->promiseMock->getState(), $promise->getState()); 291 | 292 | $this->promiseMock->shouldReceive('getState')->twice()->andReturn('rejected'); 293 | $this->assertEquals($this->promiseMock->getState(), $promise->getState()); 294 | } 295 | 296 | public function testUnsupportedAsyncRequest() 297 | { 298 | $this->expectException(\Exception::class); 299 | 300 | $this->resource->setHttpClient(Mockery::mock('Http\Client\HttpClient')); 301 | 302 | $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); 303 | } 304 | 305 | public function testGetHttpHeaders() 306 | { 307 | $headers = $this->resource->getHttpHeaders([ 308 | 'Custom-Header' => 'testing', 309 | ]); 310 | 311 | $version = NSA::getProperty($this->resource, 'version'); 312 | 313 | $this->assertEquals('SPARKPOST_API_KEY', $headers['Authorization']); 314 | $this->assertEquals('application/json', $headers['Content-Type']); 315 | $this->assertEquals('testing', $headers['Custom-Header']); 316 | $this->assertEquals('php-sparkpost/'.$version, $headers['User-Agent']); 317 | } 318 | 319 | public function testGetUrl() 320 | { 321 | $url = 'https://api.sparkpost.com:443/api/v1/transmissions?key=value 1,value 2,value 3'; 322 | $testUrl = $this->resource->getUrl('transmissions', ['key' => ['value 1', 'value 2', 'value 3']]); 323 | $this->assertEquals($url, $testUrl); 324 | } 325 | 326 | public function testSetHttpClient() 327 | { 328 | $mock = Mockery::mock(HttpClient::class); 329 | $this->resource->setHttpClient($mock); 330 | $this->assertEquals($mock, NSA::getProperty($this->resource, 'httpClient')); 331 | } 332 | 333 | public function testSetHttpAsyncClient() 334 | { 335 | $mock = Mockery::mock(HttpAsyncClient::class); 336 | $this->resource->setHttpClient($mock); 337 | $this->assertEquals($mock, NSA::getProperty($this->resource, 'httpClient')); 338 | } 339 | 340 | public function testSetHttpClientException() 341 | { 342 | $this->expectException(\Exception::class); 343 | 344 | $this->resource->setHttpClient(new \stdClass()); 345 | } 346 | 347 | public function testSetOptionsStringKey() 348 | { 349 | $this->resource->setOptions('SPARKPOST_API_KEY'); 350 | $options = NSA::getProperty($this->resource, 'options'); 351 | $this->assertEquals('SPARKPOST_API_KEY', $options['key']); 352 | } 353 | 354 | public function testSetBadOptions() 355 | { 356 | $this->expectException(\Exception::class); 357 | 358 | NSA::setProperty($this->resource, 'options', []); 359 | $this->resource->setOptions(['not' => 'SPARKPOST_API_KEY']); 360 | } 361 | 362 | public function testSetMessageFactory() 363 | { 364 | $messageFactory = Mockery::mock(MessageFactory::class); 365 | $this->resource->setMessageFactory($messageFactory); 366 | 367 | $this->assertEquals($messageFactory, NSA::invokeMethod($this->resource, 'getMessageFactory')); 368 | } 369 | } 370 | --------------------------------------------------------------------------------