├── 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 |
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' => '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' => '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' => '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'=>'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'=>'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 "NameYou 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 | --------------------------------------------------------------------------------