├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── lib └── AmazonSNS.php /.gitignore: -------------------------------------------------------------------------------- 1 | .tmp* 2 | .DS_Store* 3 | .idea/ 4 | vendor/ 5 | .project -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (C) 2011 by Chris Barr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amazon SNS PHP API 2 | 3 | [![Latest Stable Version](https://img.shields.io/packagist/v/chrisbarr/amazon-sns-php-api.svg)](https://packagist.org/packages/chrisbarr/amazon-sns-php-api) 4 | ![PHP from Packagist](https://img.shields.io/packagist/php-v/chrisbarr/amazon-sns-php-api.svg) 5 | 6 | This API wrapper is a lightweight alternative to the official [Amazon aws-sdk-for-php](http://aws.amazon.com/sdkforphp) for access to Amazon SNS (Simple Notification Service) using PHP 7 | 8 | Find out more about Amazon SNS here - http://aws.amazon.com/sns 9 | 10 | To use this wrapper you must be using PHP5 with cURL, and have an [Amazon AWS account](http://aws.amazon.com) 11 | 12 | ## Basic Use 13 | Install using [Composer](https://getcomposer.org/) on the command line: 14 | ``` 15 | $ composer require chrisbarr/amazon-sns-php-api 16 | ``` 17 | 18 | Or add it to your composer.json file: 19 | 20 | ``` 21 | { 22 | ... 23 | "require": { 24 | "chrisbarr/amazon-sns-php-api": "~1.0" 25 | } 26 | } 27 | ``` 28 | 29 | Example usage: 30 | 31 | ```php 32 | createTopic('My New SNS Topic'); 40 | 41 | // Set the Topic's Display Name (required) 42 | $AmazonSNS->setTopicAttributes($topicArn, 'DisplayName', 'My SNS Topic Display Name'); 43 | 44 | // Subscribe to this topic 45 | $AmazonSNS->subscribe($topicArn, 'email', 'example@github.com'); 46 | 47 | // And send a message to subscribers of this topic 48 | $AmazonSNS->publish($topicArn, 'Hello, world!'); 49 | ``` 50 | 51 | ## API Methods 52 | Available methods: 53 | 54 | * `addPermission($topicArn, $label, $permissions)` 55 | * `confirmSubscription($topicArn, $token)` 56 | * `createTopic($name)` 57 | * `deleteTopic($topicArn)` 58 | * `getTopicAttributes($topicArn)` 59 | * `listSubscriptions()` 60 | * `listSubscriptionsByTopic($topicArn)` 61 | * `listTopics()` 62 | * `publish($topicArn, $message, $subject, $messageStructure)` 63 | * `removePermission($topicArn, $label)` 64 | * `setTopicAttributes($topicArn, $attrName, $attrValue)` 65 | * `subscribe($topicArn, $protocol, $endpoint)` 66 | * `unsubscribe($subscriptionArn)` 67 | * `createPlatformEndpoint($platformApplicationArn, $token, $userData)` 68 | * `deleteEndpoint($deviceArn)` 69 | * `publishToEndpoint($deviceArn,$message)` 70 | 71 | To set the API region (us-east-1, us-west-2, us-west-1, eu-west-1, etc): 72 | 73 | * `setRegion($region)` 74 | 75 | *The default API region is us-east-1* 76 | 77 | ## Further Example 78 | Make sure to catch Exceptions where necessary: 79 | 80 | ```php 81 | setRegion('eu-west-1'); 86 | 87 | try { 88 | $topics = $AmazonSNS->listTopics(); 89 | } 90 | catch(SNSException $e) { 91 | // Amazon SNS returned an error 92 | echo 'SNS returned the error "' . $e->getMessage() . '" and code ' . $e->getCode(); 93 | } 94 | catch(APIException $e) { 95 | // Problem with the API 96 | echo 'There was an unknown problem with the API, returned code ' . $e->getCode(); 97 | } 98 | ``` 99 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrisbarr/amazon-sns-php-api", 3 | "type": "library", 4 | "description": "Amazon SNS PHP API", 5 | "keywords": ["aws", "amazon", "sns"], 6 | "homepage": "https://github.com/chrisbarr/AmazonSNS-PHP-API", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Chris Barr", 11 | "email": "chris.barr@ntlworld.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.2" 16 | }, 17 | "autoload": { 18 | "psr-0": { 19 | "AmazonSNS": "lib/" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/AmazonSNS.php: -------------------------------------------------------------------------------- 1 | 7 | * @link http://aws.amazon.com/sns/ 8 | * @link http://docs.amazonwebservices.com/sns/latest/api/ 9 | */ 10 | class AmazonSNS { 11 | /** @var string $access_key */ 12 | private $access_key; 13 | /** @var string $secret_key */ 14 | private $secret_key; 15 | 16 | /** @var string $protocol */ 17 | private $protocol = 'https://'; // http is allowed 18 | /** @var string $endpoint */ 19 | private $endpoint = ''; // Defaults to us-east-1 20 | 21 | /** @var array $endpoints */ 22 | private $endpoints = array( 23 | 'us-east-2' => 'sns.us-east-2.amazonaws.com', 24 | 'us-east-1' => 'sns.us-east-1.amazonaws.com', 25 | 'us-west-1' => 'sns.us-west-1.amazonaws.com', 26 | 'us-west-2' => 'sns.us-west-2.amazonaws.com', 27 | 'ap-northeast-1' => 'sns.ap-northeast-1.amazonaws.com', 28 | 'ap-northeast-2' => 'sns.ap-northeast-2.amazonaws.com', 29 | 'ap-northeast-3' => 'sns.ap-northeast-3.amazonaws.com', 30 | 'ap-south-1' => 'sns.ap-south-1.amazonaws.com', 31 | 'ap-southeast-1' => 'sns.ap-southeast-1.amazonaws.com', 32 | 'ap-southeast-2' => 'sns.ap-southeast-2.amazonaws.com', 33 | 'ca-central-1' => 'sns.ca-central-1.amazonaws.com', 34 | 'cn-north-1' => 'sns.cn-north-1.amazonaws.com.cn', 35 | 'cn-northwest-1' => 'sns.cn-northwest-1.amazonaws.com.cn', 36 | 'eu-central-1' => 'sns.eu-central-1.amazonaws.com', 37 | 'eu-west-1' => 'sns.eu-west-1.amazonaws.com', 38 | 'eu-west-2' => 'sns.eu-west-2.amazonaws.com', 39 | 'eu-west-3' => 'sns.eu-west-3.amazonaws.com', 40 | 'sa-east-1' => 'sns.sa-east-1.amazonaws.com', 41 | 'us-gov-west-1' => 'sns.us-gov-west-1.amazonaws.com', 42 | ); 43 | 44 | /** 45 | * Instantiate the object - set access_key and secret_key and set default region 46 | * 47 | * @param string $access_key 48 | * @param string $secret_key 49 | * @param string $region [optional] 50 | * @throws InvalidArgumentException 51 | */ 52 | public function __construct($access_key, $secret_key, $region = 'us-east-1') { 53 | $this->access_key = $access_key; 54 | $this->secret_key = $secret_key; 55 | 56 | if(empty($this->access_key) || empty($this->secret_key)) { 57 | throw new InvalidArgumentException('Must define Amazon access key and secret key'); 58 | } 59 | 60 | $this->setRegion($region); 61 | } 62 | 63 | /** 64 | * Set the SNS endpoint/region 65 | * 66 | * @link http://docs.amazonwebservices.com/general/latest/gr/index.html?rande.html 67 | * @param string $region 68 | * @return string 69 | * @throws InvalidArgumentException 70 | */ 71 | public function setRegion($region) { 72 | if(!isset($this->endpoints[$region])) { 73 | throw new InvalidArgumentException('Region unrecognised'); 74 | } 75 | 76 | return $this->endpoint = $this->protocol . $this->endpoints[$region]; 77 | } 78 | 79 | /** 80 | * Manually set the AWS API endpoint 81 | * 82 | * @param string $endpoint 83 | */ 84 | public function setEndpoint($endpoint) { 85 | $this->endpoint = $endpoint; 86 | } 87 | 88 | 89 | // 90 | // Public interface functions 91 | // 92 | 93 | /** 94 | * Add permissions to a topic 95 | * 96 | * Example: 97 | * $AmazonSNS->addPermission('topic:arn:123', 'New Permission', array('987654321000' => 'Publish', '876543210000' => array('Publish', 'SetTopicAttributes'))); 98 | * 99 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_AddPermission.html 100 | * @param string $topicArn 101 | * @param string $label Unique name of permissions 102 | * @param array $permissions [optional] Array of permissions - member ID as keys, actions as values 103 | * @return bool 104 | * @throws InvalidArgumentException 105 | */ 106 | public function addPermission($topicArn, $label, $permissions = array()) { 107 | if(empty($topicArn) || empty($label)) { 108 | throw new InvalidArgumentException('Must supply TopicARN and a Label for this permission'); 109 | } 110 | 111 | // Add standard params as normal 112 | $params = array( 113 | 'TopicArn' => $topicArn, 114 | 'Label' => $label 115 | ); 116 | 117 | // Compile permissions into separate sequential arrays 118 | $memberFlatArray = array(); 119 | $permissionFlatArray = array(); 120 | 121 | foreach($permissions as $member => $permission) { 122 | if(is_array($permission)) { 123 | // Array of permissions 124 | foreach($permission as $singlePermission) { 125 | $memberFlatArray[] = $member; 126 | $permissionFlatArray[] = $singlePermission; 127 | } 128 | } 129 | else { 130 | // Just a single permission 131 | $memberFlatArray[] = $member; 132 | $permissionFlatArray[] = $permission; 133 | } 134 | } 135 | 136 | // Dummy check 137 | if(count($memberFlatArray) !== count($permissionFlatArray)) { 138 | // Something went wrong 139 | throw new InvalidArgumentException('Mismatch of permissions to users'); 140 | } 141 | 142 | // Finally add to params 143 | for($x = 1; $x <= count($memberFlatArray); $x++) { 144 | $params['ActionName.member.' . $x] = $permissionFlatArray[$x]; 145 | $params['AWSAccountID.member.' . $x] = $memberFlatArray[$x]; 146 | } 147 | 148 | // Finally send request 149 | $this->_request('AddPermission', $params); 150 | 151 | return true; 152 | } 153 | 154 | /** 155 | * Confirm a subscription to a topic 156 | * 157 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_ConfirmSubscription.html 158 | * @param string $topicArn 159 | * @param string $token 160 | * @param bool|null $authenticateOnUnsubscribe [optional] 161 | * @return string - SubscriptionARN 162 | * @throws InvalidArgumentException 163 | */ 164 | public function confirmSubscription($topicArn, $token, $authenticateOnUnsubscribe = null) { 165 | if(empty($topicArn) || empty($token)) { 166 | throw new InvalidArgumentException('Must supply a TopicARN and a Token to confirm subscription'); 167 | } 168 | 169 | $params = array( 170 | 'TopicArn' => $topicArn, 171 | 'Token' => $token 172 | ); 173 | 174 | if(!is_null($authenticateOnUnsubscribe)) { 175 | $params['AuthenticateOnUnsubscribe'] = $authenticateOnUnsubscribe; 176 | } 177 | 178 | $resultXml = $this->_request('ConfirmSubscription', $params); 179 | 180 | return strval($resultXml->ConfirmSubscriptionResult->SubscriptionArn); 181 | } 182 | 183 | /** 184 | * Create an SNS topic 185 | * 186 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_CreateTopic.html 187 | * @param string $name 188 | * @return string - TopicARN 189 | * @throws InvalidArgumentException 190 | */ 191 | public function createTopic($name) { 192 | if(empty($name)) { 193 | throw new InvalidArgumentException('Must supply a Name to create topic'); 194 | } 195 | 196 | $resultXml = $this->_request('CreateTopic', array('Name' => $name)); 197 | 198 | return strval($resultXml->CreateTopicResult->TopicArn); 199 | } 200 | 201 | /** 202 | * Delete an SNS topic 203 | * 204 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_DeleteTopic.html 205 | * @param string $topicArn 206 | * @return bool 207 | * @throws InvalidArgumentException 208 | */ 209 | public function deleteTopic($topicArn) { 210 | if(empty($topicArn)) { 211 | throw new InvalidArgumentException('Must supply a TopicARN to delete a topic'); 212 | } 213 | 214 | $this->_request('DeleteTopic', array('TopicArn' => $topicArn)); 215 | 216 | return true; 217 | } 218 | 219 | /** 220 | * Get the attributes of a topic like owner, ACL, display name 221 | * 222 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_GetTopicAttributes.html 223 | * @param string $topicArn 224 | * @return array 225 | * @throws InvalidArgumentException 226 | */ 227 | public function getTopicAttributes($topicArn) { 228 | if(empty($topicArn)) { 229 | throw new InvalidArgumentException('Must supply a TopicARN to get topic attributes'); 230 | } 231 | 232 | $resultXml = $this->_request('GetTopicAttributes', array('TopicArn' => $topicArn)); 233 | 234 | // Get attributes 235 | $attributes = $resultXml->GetTopicAttributesResult->Attributes->entry; 236 | 237 | // Unfortunately cannot use _processXmlToArray here, so process manually 238 | $returnArray = array(); 239 | 240 | // Process into array 241 | foreach($attributes as $attribute) { 242 | // Store attribute key as array key 243 | $returnArray[strval($attribute->key)] = strval($attribute->value); 244 | } 245 | 246 | return $returnArray; 247 | } 248 | 249 | /** 250 | * List subscriptions that user is subscribed to 251 | * 252 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_ListSubscriptions.html 253 | * @param string|null $nextToken [optional] Token to retrieve next page of results 254 | * @return array 255 | */ 256 | public function listSubscriptions($nextToken = null) { 257 | $params = array(); 258 | 259 | if(!is_null($nextToken)) { 260 | $params['NextToken'] = $nextToken; 261 | } 262 | 263 | $resultXml = $this->_request('ListSubscriptions', $params); 264 | 265 | // Get subscriptions 266 | $subs = $resultXml->ListSubscriptionsResult->Subscriptions->member; 267 | 268 | $return = ['members' => $this->_processXmlToArray($subs)]; 269 | 270 | if(isset($resultXml->ListSubscriptionsResult->NextToken)) { 271 | $return['nextToken'] = strval($resultXml->ListSubscriptionsResult->NextToken); 272 | } 273 | 274 | return $return; 275 | } 276 | 277 | /** 278 | * List subscribers to a topic 279 | * 280 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_ListSubscriptionsByTopic.html 281 | * @param string $topicArn 282 | * @param string|null $nextToken [optional] Token to retrieve next page of results 283 | * @return array 284 | * @throws InvalidArgumentException 285 | */ 286 | public function listSubscriptionsByTopic($topicArn, $nextToken = null) { 287 | if(empty($topicArn)) { 288 | throw new InvalidArgumentException('Must supply a TopicARN to show subscriptions to a topic'); 289 | } 290 | 291 | $params = array( 292 | 'TopicArn' => $topicArn 293 | ); 294 | 295 | if(!is_null($nextToken)) { 296 | $params['NextToken'] = $nextToken; 297 | } 298 | 299 | $resultXml = $this->_request('ListSubscriptionsByTopic', $params); 300 | 301 | // Get subscriptions 302 | $subs = $resultXml->ListSubscriptionsByTopicResult->Subscriptions->member; 303 | 304 | $return = ['members' => $this->_processXmlToArray($subs)]; 305 | 306 | if(isset($resultXml->ListSubscriptionsByTopicResult->NextToken)) { 307 | $return['nextToken'] = strval($resultXml->ListSubscriptionsByTopicResult->NextToken); 308 | } 309 | 310 | return $return; 311 | } 312 | 313 | /** 314 | * List SNS topics 315 | * 316 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_ListTopics.html 317 | * @param string|null $nextToken [optional] Token to retrieve next page of results 318 | * @return array 319 | */ 320 | public function listTopics($nextToken = null) { 321 | $params = array(); 322 | 323 | if(!is_null($nextToken)) { 324 | $params['NextToken'] = $nextToken; 325 | } 326 | 327 | $resultXml = $this->_request('ListTopics', $params); 328 | 329 | // Get Topics 330 | $topics = $resultXml->ListTopicsResult->Topics->member; 331 | 332 | return $this->_processXmlToArray($topics); 333 | } 334 | 335 | /** 336 | * Publish a message to a topic 337 | * 338 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_Publish.html 339 | * @param string $topicArn 340 | * @param string $message 341 | * @param string $subject [optional] Used when sending emails 342 | * @param string $messageStructure [optional] Used when you want to send a different message for each protocol.If you set MessageStructure to json, the value of the Message parameter must: be a syntactically valid JSON object; and contain at least a top-level JSON key of "default" with a value that is a string. 343 | * @return string 344 | * @throws InvalidArgumentException 345 | */ 346 | public function publish($topicArn, $message, $subject = '', $messageStructure = '') { 347 | if(empty($topicArn) || empty($message)) { 348 | throw new InvalidArgumentException('Must supply a TopicARN and Message to publish to a topic'); 349 | } 350 | 351 | $params = array( 352 | 'TopicArn' => $topicArn, 353 | 'Message' => $message 354 | ); 355 | 356 | if(!empty($subject)) { 357 | $params['Subject'] = $subject; 358 | } 359 | 360 | if(!empty($messageStructure)) { 361 | $params['MessageStructure'] = $messageStructure; 362 | } 363 | 364 | $resultXml = $this->_request('Publish', $params); 365 | 366 | return strval($resultXml->PublishResult->MessageId); 367 | } 368 | 369 | /** 370 | * Remove a set of permissions indentified by topic and label that was used when creating permissions 371 | * 372 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_RemovePermission.html 373 | * @param string $topicArn 374 | * @param string $label 375 | * @return bool 376 | * @throws InvalidArgumentException 377 | */ 378 | public function removePermission($topicArn, $label) { 379 | if(empty($topicArn) || empty($label)) { 380 | throw new InvalidArgumentException('Must supply a TopicARN and Label to remove a permission'); 381 | } 382 | 383 | $this->_request('RemovePermission', array('Label' => $label)); 384 | 385 | return true; 386 | } 387 | 388 | /** 389 | * Set a single attribute on a topic 390 | * 391 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_SetTopicAttributes.html 392 | * @param string $topicArn 393 | * @param string $attrName 394 | * @param mixed $attrValue 395 | * @return bool 396 | * @throws InvalidArgumentException 397 | */ 398 | public function setTopicAttributes($topicArn, $attrName, $attrValue) { 399 | if(empty($topicArn) || empty($attrName) || empty($attrValue)) { 400 | throw new InvalidArgumentException('Must supply a TopicARN, AttributeName and AttributeValue to set a topic attribute'); 401 | } 402 | 403 | $this->_request('SetTopicAttributes', array( 404 | 'TopicArn' => $topicArn, 405 | 'AttributeName' => $attrName, 406 | 'AttributeValue' => $attrValue 407 | )); 408 | 409 | return true; 410 | } 411 | 412 | /** 413 | * Subscribe to a topic 414 | * 415 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_Subscribe.html 416 | * @param string $topicArn 417 | * @param string $protocol - http/https/email/email-json/sms/sqs 418 | * @param string $endpoint 419 | * @return string $SubscriptionArn 420 | * @throws InvalidArgumentException 421 | */ 422 | public function subscribe($topicArn, $protocol, $endpoint) { 423 | if(empty($topicArn) || empty($protocol) || empty($endpoint)) { 424 | throw new InvalidArgumentException('Must supply a TopicARN, Protocol and Endpoint to subscribe to a topic'); 425 | } 426 | 427 | $response = $this->_request('Subscribe', array( 428 | 'TopicArn' => $topicArn, 429 | 'Protocol' => $protocol, 430 | 'Endpoint' => $endpoint 431 | )); 432 | 433 | return strval($response->SubscribeResult->SubscriptionArn); 434 | } 435 | 436 | /** 437 | * Unsubscribe a user from a topic 438 | * 439 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_Unsubscribe.html 440 | * @param string $subscriptionArn 441 | * @return bool 442 | * @throws InvalidArgumentException 443 | */ 444 | public function unsubscribe($subscriptionArn) { 445 | if(empty($subscriptionArn)) { 446 | throw new InvalidArgumentException('Must supply a SubscriptionARN to unsubscribe from a topic'); 447 | } 448 | 449 | $this->_request('Unsubscribe', array('SubscriptionArn' => $subscriptionArn)); 450 | 451 | return true; 452 | } 453 | 454 | /** 455 | * Create Platform endpoint 456 | * 457 | * @link http://docs.aws.amazon.com/sns/latest/api/API_CreatePlatformEndpoint.html 458 | * @param string $platformApplicationArn 459 | * @param string $token 460 | * @param string $userData 461 | * @return string 462 | * @throws InvalidArgumentException 463 | */ 464 | public function createPlatformEndpoint($platformApplicationArn, $token, $userData = null) { 465 | if(empty($platformApplicationArn) || empty($token)) { 466 | throw new InvalidArgumentException('Must supply a PlatformApplicationArn & Token to create platform endpoint'); 467 | } 468 | 469 | $attributes = array( 470 | 'PlatformApplicationArn' => $platformApplicationArn, 471 | 'Token' => $token 472 | ); 473 | 474 | if(!empty($userData)) { 475 | $attributes['CustomUserData'] = $userData; 476 | } 477 | 478 | $response = $this->_request('CreatePlatformEndpoint', $attributes); 479 | 480 | return strval($response->CreatePlatformEndpointResult->EndpointArn); 481 | } 482 | 483 | /** 484 | * Delete endpoint 485 | * 486 | * @link http://docs.aws.amazon.com/sns/latest/api/API_DeleteEndpoint.html 487 | * @param string $deviceArn 488 | * 489 | * @return bool 490 | * @throws InvalidArgumentException 491 | */ 492 | public function deleteEndpoint($deviceArn) { 493 | if(empty($deviceArn)) { 494 | throw new InvalidArgumentException('Must supply a DeviceARN to remove platform endpoint'); 495 | } 496 | 497 | $this->_request('DeleteEndpoint', array( 498 | 'EndpointArn' => $deviceArn 499 | )); 500 | 501 | return true; 502 | } 503 | 504 | /** 505 | * Publish a message to an Endpoint 506 | * 507 | * @link http://docs.amazonwebservices.com/sns/latest/api/API_Publish.html 508 | * @param string $deviceArn 509 | * @param string $message 510 | * @return string 511 | * @throws InvalidArgumentException 512 | */ 513 | public function publishToEndpoint($deviceArn, $message) { 514 | if (empty($deviceArn) || empty($message)) { 515 | throw new InvalidArgumentException('Must supply DeviceArn and Message'); 516 | } 517 | 518 | $resultXml = $this->_request('Publish', array( 519 | 'TargetArn' => $deviceArn, 520 | 'Message' => $message) 521 | ); 522 | 523 | return strval($resultXml->PublishResult->MessageId); 524 | } 525 | 526 | // 527 | // Private functions 528 | // 529 | 530 | /** 531 | * Perform and process a cURL request 532 | * 533 | * @param string $action 534 | * @param array $params [optional] 535 | * @return SimpleXMLElement 536 | * @throws SNSException|APIException 537 | */ 538 | private function _request($action, $params = array()) { 539 | // Add in required params 540 | $params['Action'] = $action; 541 | $params['AWSAccessKeyId'] = $this->access_key; 542 | $params['Timestamp'] = gmdate('Y-m-d\TH:i:s.000\Z'); 543 | $params['SignatureVersion'] = 2; 544 | $params['SignatureMethod'] = 'HmacSHA256'; 545 | 546 | // Sort and encode into string 547 | uksort($params, 'strnatcmp'); 548 | $queryString = ''; 549 | foreach ($params as $key => $val) { 550 | $queryString .= "&{$key}=".rawurlencode($val); 551 | } 552 | $queryString = substr($queryString, 1); 553 | 554 | // Form request string 555 | $requestString = "GET\n" 556 | . $this->endpoint."\n" 557 | . "/\n" 558 | . $queryString; 559 | 560 | // Create signature - Version 2 561 | $params['Signature'] = base64_encode( 562 | hash_hmac('sha256', $requestString, $this->secret_key, true) 563 | ); 564 | 565 | // Finally create request 566 | $request = $this->endpoint . '/?' . http_build_query($params, '', '&'); 567 | 568 | // Instantiate cUrl and perform request 569 | $ch = curl_init(); 570 | curl_setopt($ch, CURLOPT_URL, $request); 571 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 572 | 573 | $output = curl_exec($ch); 574 | $info = curl_getinfo($ch); 575 | 576 | // Close cUrl 577 | curl_close($ch); 578 | 579 | // Load XML response 580 | $xmlResponse = simplexml_load_string($output); 581 | 582 | // Check return code 583 | if($this->_checkGoodResponse($info['http_code']) === false) { 584 | // Response not in 200 range 585 | if(isset($xmlResponse->Error)) { 586 | // Amazon returned an XML error 587 | throw new SNSException(strval($xmlResponse->Error->Code) . ': ' . strval($xmlResponse->Error->Message), $info['http_code']); 588 | } 589 | else { 590 | // Some other problem 591 | throw new APIException('There was a problem executing this request', $info['http_code']); 592 | } 593 | } 594 | else { 595 | // All good 596 | return $xmlResponse; 597 | } 598 | } 599 | 600 | /** 601 | * Check the curl response code - anything in 200 range 602 | * 603 | * @param int $code 604 | * @return bool 605 | */ 606 | private function _checkGoodResponse($code) { 607 | return floor($code / 100) == 2; 608 | } 609 | 610 | /** 611 | * Transform the standard AmazonSNS XML array format into a normal array 612 | * 613 | * @param SimpleXMLElement $xmlArray 614 | * @return array 615 | */ 616 | private function _processXmlToArray(SimpleXMLElement $xmlArray) { 617 | $returnArray = array(); 618 | 619 | // Process into array 620 | foreach($xmlArray as $xmlElement) { 621 | $elementArray = array(); 622 | 623 | // Loop through each element 624 | foreach($xmlElement as $key => $element) { 625 | // Use strval() to make sure no SimpleXMLElement objects remain 626 | $elementArray[$key] = strval($element); 627 | } 628 | 629 | // Store array of elements 630 | $returnArray[] = $elementArray; 631 | } 632 | 633 | return $returnArray; 634 | } 635 | } 636 | 637 | // Exception thrown if there's a problem with the API 638 | class APIException extends Exception {} 639 | 640 | // Exception thrown if Amazon returns an error 641 | class SNSException extends Exception {} 642 | --------------------------------------------------------------------------------