├── .gitignore ├── phpcs.xml ├── .travis.yml ├── src ├── Exception │ ├── RuntimeException.php │ └── InvalidArgumentException.php └── Gcm │ ├── Client.php │ ├── Response.php │ └── Message.php ├── phpunit.xml.dist ├── README.md ├── composer.json ├── LICENSE.md ├── CHANGELOG.md └── test └── Gcm ├── ResponseTest.php ├── MessageTest.php └── ClientTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | composer.lock 3 | composer.phar 4 | vendor/ 5 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src 6 | test 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | matrix: 4 | include: 5 | - php: 5.6 6 | - php: 7.0 7 | - php: 7.1 8 | env: 9 | - CS_CHECK=true 10 | - php: 7.2 11 | - php: 7.3 12 | 13 | before_install: 14 | - composer install --no-interaction 15 | 16 | script: 17 | - ./vendor/bin/phpunit 18 | - if [[ $CS_CHECK == 'true' ]]; then ./vendor/bin/phpcs ; fi 19 | 20 | notifications: 21 | email: false 22 | -------------------------------------------------------------------------------- /src/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | ./test 9 | 10 | 11 | 12 | 13 | 14 | ./src 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ZendService\Google\Gcm [![Build Status](https://api.travis-ci.org/zendframework/ZendService_Google_Gcm.svg?branch=develop)](https://travis-ci.org/zendframework/ZendService_Google_Gcm) 2 | ================================ 3 | 4 | > ## Repository abandoned 2019-12-05 5 | > 6 | > This repository is no longer maintained. 7 | 8 | Provides support for Google push notifications. 9 | 10 | ## Requirements 11 | 12 | * PHP >= 5.6 13 | 14 | ## Getting Started 15 | 16 | Install this library using [Composer](http://getcomposer.org/): 17 | 18 | ```bash 19 | $ composer require zendframework/zendservice-google-gcm 20 | ``` 21 | 22 | ## Documentation 23 | 24 | The documentation can be found at: http://framework.zend.com/manual/current/en/modules/zendservice.google.gcm.html -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zendframework/zendservice-google-gcm", 3 | "description": "OOP wrapper for Google Cloud Messaging", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "zf", 7 | "zendframework", 8 | "gcm", 9 | "push", 10 | "notification", 11 | "google" 12 | ], 13 | "support": { 14 | "issues": "https://github.com/zendframework/ZendService_Google_Gcm/issues", 15 | "source": "https://github.com/zendframework/ZendService_Google_Gcm", 16 | "rss": "https://github.com/zendframework/ZendService_Google_Gcm/releases.atom", 17 | "chat": "https://zendframework-slack.herokuapp.com", 18 | "forum": "https://discourse.zendframework.com/c/questions/components" 19 | }, 20 | "require": { 21 | "php": "^5.6 || ^7.0", 22 | "zendframework/zend-http": "^2.0", 23 | "zendframework/zend-json": "^2.0 || ^3.0" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.5", 27 | "zendframework/zend-coding-standard": "~1.0.0" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "ZendService\\Google\\": "src/" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "ZendServiceTest\\Google\\": "test/" 37 | } 38 | }, 39 | "config": { 40 | "sort-packages": true 41 | }, 42 | "extra": { 43 | "branch-alias": { 44 | "dev-master": "2.1.x-dev", 45 | "dev-develop": "2.2.x-dev" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2018, Zend Technologies USA, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 2.1.2 - TBD 6 | 7 | ### Added 8 | 9 | - Nothing. 10 | 11 | ### Changed 12 | 13 | - Nothing. 14 | 15 | ### Deprecated 16 | 17 | - Nothing. 18 | 19 | ### Removed 20 | 21 | - Nothing. 22 | 23 | ### Fixed 24 | 25 | - Nothing. 26 | 27 | ## 2.1.1 - 2019-02-07 28 | 29 | ### Added 30 | 31 | - [#37](https://github.com/zendframework/ZendService_Google_Gcm/pull/37) adds support for PHP 7.3. 32 | 33 | - [#38](https://github.com/zendframework/ZendService_Google_Gcm/pull/38) adds support for zend-json v3 releases. 34 | 35 | ### Changed 36 | 37 | - Nothing. 38 | 39 | ### Deprecated 40 | 41 | - Nothing. 42 | 43 | ### Removed 44 | 45 | - Nothing. 46 | 47 | ### Fixed 48 | 49 | - Nothing. 50 | 51 | ## 2.1.0 - 2018-05-08 52 | 53 | ### Added 54 | 55 | - [#35](https://github.com/zendframework/ZendService_Google_Gcm/pull/35) adds support for PHP 7.1 and 7.2. 56 | 57 | - [#13](https://github.com/zendframework/ZendService_Google_Gcm/pull/13) adds constants mapping to common GCM error codes as `ZendService\Gcm\Response::ERROR_*`. 58 | 59 | ### Changed 60 | 61 | - Nothing. 62 | 63 | ### Deprecated 64 | 65 | - Nothing. 66 | 67 | ### Removed 68 | 69 | - [#35](https://github.com/zendframework/ZendService_Google_Gcm/pull/35) removes support for PHP 5.5. 70 | 71 | - [#35](https://github.com/zendframework/ZendService_Google_Gcm/pull/35) removes support for HHVM. 72 | 73 | ### Fixed 74 | 75 | - [#18](https://github.com/zendframework/ZendService_Google_Gcm/pull/18) adds a `Content-Length` header with the message length prior to sending 76 | messages to GCM; this fixes 411 errors previously observed. 77 | 78 | ## 2.0.0 - 2017-01-17 79 | 80 | ### Added 81 | 82 | - [#27](https://github.com/zendframework/ZendService_Google_Gcm/pull/27) PSR-4 schema 83 | - [#27](https://github.com/zendframework/ZendService_Google_Gcm/pull/27) PHP >= 5.5 & 7 84 | - [#20](https://github.com/zendframework/ZendService_Google_Gcm/pull/25) Notification and priority parameters for FCM 85 | 86 | ### Deprecated 87 | 88 | - Nothing. 89 | 90 | ### Removed 91 | 92 | - Nothing. 93 | 94 | ### Fixed 95 | 96 | - [#27](https://github.com/zendframework/ZendService_Google_Gcm/pull/27) Fix travis CI integration 97 | - [#27](https://github.com/zendframework/ZendService_Google_Gcm/pull/27) Fix coding style (use ::class and short arrays) 98 | - [#27](https://github.com/zendframework/ZendService_Google_Gcm/pull/27) Fix docblocks for IDE integration 99 | - [#20](https://github.com/zendframework/ZendService_Google_Gcm/pull/25) Change endpoint to FCM 100 | 101 | ## 1.0.3 - 2015-10-13 102 | 103 | ### Added 104 | 105 | - Nothing. 106 | 107 | ### Deprecated 108 | 109 | - Nothing. 110 | 111 | ### Removed 112 | 113 | - Nothing. 114 | 115 | ### Fixed 116 | 117 | - [#12](https://github.com/zendframework/ZendService_Google_Gcm/pull/12) - 118 | Updated GCM URL. 119 | -------------------------------------------------------------------------------- /test/Gcm/ResponseTest.php: -------------------------------------------------------------------------------- 1 | m = new Message(); 34 | } 35 | 36 | public function testConstructorExpectedBehavior() 37 | { 38 | $response = new Response(); 39 | self::assertNull($response->getResponse()); 40 | self::assertNull($response->getMessage()); 41 | 42 | $message = new Message(); 43 | $response = new Response(null, $message); 44 | self::assertEquals($message, $response->getMessage()); 45 | self::assertNull($response->getResponse()); 46 | 47 | $message = new Message(); 48 | $responseArray = [ 49 | 'results' => [ 50 | ['message_id' => '1:1234'], 51 | ], 52 | 'success' => 1, 53 | 'failure' => 0, 54 | 'canonical_ids' => 0, 55 | 'multicast_id' => 1, 56 | ]; 57 | $response = new Response($responseArray, $message); 58 | self::assertEquals($responseArray, $response->getResponse()); 59 | self::assertEquals($message, $response->getMessage()); 60 | } 61 | 62 | public function testInvalidConstructorThrowsException() 63 | { 64 | if (PHP_VERSION_ID < 70000) { 65 | self::markTestSkipped('PHP 7 required.'); 66 | } 67 | 68 | $this->expectException(\TypeError::class); 69 | new Response('{bad'); 70 | } 71 | 72 | public function testInvalidConstructorThrowsExceptionOnPhp7() 73 | { 74 | if (PHP_VERSION_ID >= 70000) { 75 | self::markTestSkipped('PHP >=5.5 required.'); 76 | } 77 | 78 | $this->expectException(\PHPUnit_Framework_Error::class); 79 | new Response('{bad'); 80 | } 81 | 82 | public function testMessageExpectedBehavior() 83 | { 84 | $message = new Message(); 85 | $response = new Response(); 86 | $response->setMessage($message); 87 | self::assertEquals($message, $response->getMessage()); 88 | } 89 | 90 | public function testResponse() 91 | { 92 | $responseArr = [ 93 | 'results' => [ 94 | ['message_id' => '1:234'], 95 | ], 96 | 'success' => 1, 97 | 'failure' => 0, 98 | 'canonical_ids' => 0, 99 | 'multicast_id' => '123', 100 | ]; 101 | $response = new Response(); 102 | $response->setResponse($responseArr); 103 | self::assertEquals($responseArr, $response->getResponse()); 104 | self::assertEquals(1, $response->getSuccessCount()); 105 | self::assertEquals(0, $response->getFailureCount()); 106 | self::assertEquals(0, $response->getCanonicalCount()); 107 | // test results non correlated 108 | $expected = [['message_id' => '1:234']]; 109 | self::assertEquals($expected, $response->getResults()); 110 | $expected = [0 => '1:234']; 111 | self::assertEquals($expected, $response->getResult(Response::RESULT_MESSAGE_ID)); 112 | 113 | $message = new Message(); 114 | $message->setRegistrationIds(['ABCDEF']); 115 | $response->setMessage($message); 116 | $expected = ['ABCDEF' => '1:234']; 117 | self::assertEquals($expected, $response->getResult(Response::RESULT_MESSAGE_ID)); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Gcm/Client.php: -------------------------------------------------------------------------------- 1 | apiKey; 50 | } 51 | 52 | /** 53 | * Set API Key. 54 | * 55 | * @param string $apiKey 56 | * 57 | * @return Client 58 | * 59 | * @throws Exception\InvalidArgumentException 60 | */ 61 | public function setApiKey($apiKey) 62 | { 63 | if (! is_string($apiKey) || empty($apiKey)) { 64 | throw new Exception\InvalidArgumentException('The api key must be a string and not empty'); 65 | } 66 | $this->apiKey = $apiKey; 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Get HTTP Client. 73 | * 74 | * @throws \Zend\Http\Client\Exception\InvalidArgumentException 75 | * 76 | * @return \Zend\Http\Client 77 | */ 78 | public function getHttpClient() 79 | { 80 | if (! $this->httpClient) { 81 | $this->httpClient = new HttpClient(); 82 | $this->httpClient->setOptions(['strictredirects' => true]); 83 | } 84 | 85 | return $this->httpClient; 86 | } 87 | 88 | /** 89 | * Set HTTP Client. 90 | * 91 | * @param \Zend\Http\Client 92 | * 93 | * @return Client 94 | */ 95 | public function setHttpClient(HttpClient $http) 96 | { 97 | $this->httpClient = $http; 98 | 99 | return $this; 100 | } 101 | 102 | /** 103 | * Send Message. 104 | * 105 | * @param Message $message 106 | * 107 | * @throws \Zend\Json\Exception\RuntimeException 108 | * @throws \ZendService\Google\Exception\RuntimeException 109 | * @throws \Zend\Http\Exception\RuntimeException 110 | * @throws \Zend\Http\Client\Exception\RuntimeException 111 | * @throws \Zend\Http\Exception\InvalidArgumentException 112 | * @throws \Zend\Http\Client\Exception\InvalidArgumentException 113 | * @throws \ZendService\Google\Exception\InvalidArgumentException 114 | * 115 | * @return Response 116 | */ 117 | public function send(Message $message) 118 | { 119 | $client = $this->getHttpClient(); 120 | $client->setUri(self::SERVER_URI); 121 | $headers = $client->getRequest()->getHeaders(); 122 | $headers->addHeaderLine('Authorization', 'key=' . $this->getApiKey()); 123 | $headers->addHeaderLine('Content-length', mb_strlen($message->toJson())); 124 | 125 | $response = $client->setHeaders($headers) 126 | ->setMethod('POST') 127 | ->setRawBody($message->toJson()) 128 | ->setEncType('application/json') 129 | ->send(); 130 | 131 | switch ($response->getStatusCode()) { 132 | case 500: 133 | throw new Exception\RuntimeException('500 Internal Server Error'); 134 | break; 135 | case 503: 136 | $exceptionMessage = '503 Server Unavailable'; 137 | if ($retry = $response->getHeaders()->get('Retry-After')) { 138 | $exceptionMessage .= '; Retry After: '.$retry; 139 | } 140 | throw new Exception\RuntimeException($exceptionMessage); 141 | break; 142 | case 401: 143 | throw new Exception\RuntimeException('401 Forbidden; Authentication Error'); 144 | break; 145 | case 400: 146 | throw new Exception\RuntimeException('400 Bad Request; invalid message'); 147 | break; 148 | } 149 | 150 | if (! $response = Json::decode($response->getBody(), Json::TYPE_ARRAY)) { 151 | throw new Exception\RuntimeException('Response body did not contain a valid JSON response'); 152 | } 153 | 154 | return new Response($response, $message); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Gcm/Response.php: -------------------------------------------------------------------------------- 1 | setResponse($response); 107 | } 108 | 109 | if ($message) { 110 | $this->setMessage($message); 111 | } 112 | } 113 | 114 | /** 115 | * Get Message. 116 | * 117 | * @return Message 118 | */ 119 | public function getMessage() 120 | { 121 | return $this->message; 122 | } 123 | 124 | /** 125 | * Set Message. 126 | * 127 | * @param Message $message 128 | * 129 | * @return Response 130 | */ 131 | public function setMessage(Message $message) 132 | { 133 | $this->message = $message; 134 | 135 | return $this; 136 | } 137 | 138 | /** 139 | * Get Response. 140 | * 141 | * @return array 142 | */ 143 | public function getResponse() 144 | { 145 | return $this->response; 146 | } 147 | 148 | /** 149 | * Set Response. 150 | * 151 | * @param array $response 152 | * 153 | * @return Response 154 | * 155 | * @throws Exception\InvalidArgumentException 156 | */ 157 | public function setResponse(array $response) 158 | { 159 | if (! isset( 160 | $response['results'], 161 | $response['success'], 162 | $response['failure'], 163 | $response['canonical_ids'], 164 | $response['multicast_id'] 165 | )) { 166 | throw new Exception\InvalidArgumentException('Response did not contain the proper fields'); 167 | } 168 | 169 | $this->response = $response; 170 | $this->results = $response['results']; 171 | $this->cntSuccess = (int) $response['success']; 172 | $this->cntFailure = (int) $response['failure']; 173 | $this->cntCanonical = (int) $response['canonical_ids']; 174 | $this->id = (int) $response['multicast_id']; 175 | 176 | return $this; 177 | } 178 | 179 | /** 180 | * Get Success Count. 181 | * 182 | * @return int 183 | */ 184 | public function getSuccessCount() 185 | { 186 | return $this->cntSuccess; 187 | } 188 | 189 | /** 190 | * Get Failure Count. 191 | * 192 | * @return int 193 | */ 194 | public function getFailureCount() 195 | { 196 | return $this->cntFailure; 197 | } 198 | 199 | /** 200 | * Get Canonical Count. 201 | * 202 | * @return int 203 | */ 204 | public function getCanonicalCount() 205 | { 206 | return $this->cntCanonical; 207 | } 208 | 209 | /** 210 | * Get Results. 211 | * 212 | * @return array multi dimensional array of: 213 | * NOTE: key is registration_id if the message is passed. 214 | * 'registration_id' => [ 215 | * 'message_id' => 'id', 216 | * 'error' => 'error', 217 | * 'registration_id' => 'id' 218 | * ] 219 | */ 220 | public function getResults() 221 | { 222 | return $this->correlate(); 223 | } 224 | 225 | /** 226 | * Get Singular Result. 227 | * 228 | * @param int $flag one of the RESULT_* flags 229 | * 230 | * @return array singular array with keys being registration id 231 | * value is the type of result 232 | */ 233 | public function getResult($flag) 234 | { 235 | $ret = []; 236 | foreach ($this->correlate() as $k => $v) { 237 | if (isset($v[$flag])) { 238 | $ret[$k] = $v[$flag]; 239 | } 240 | } 241 | 242 | return $ret; 243 | } 244 | 245 | /** 246 | * Correlate Message and Result. 247 | * 248 | * @return array 249 | */ 250 | protected function correlate() 251 | { 252 | $results = $this->results; 253 | if ($this->message && $results) { 254 | $ids = $this->message->getRegistrationIds(); 255 | while ($id = array_shift($ids)) { 256 | $results[$id] = array_shift($results); 257 | } 258 | } 259 | 260 | return $results; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /test/Gcm/MessageTest.php: -------------------------------------------------------------------------------- 1 | 'value', 32 | 'key2' => [ 33 | 'value', 34 | ], 35 | ]; 36 | 37 | /** 38 | * @var Message 39 | */ 40 | private $m; 41 | 42 | public function setUp() 43 | { 44 | $this->m = new Message(); 45 | } 46 | 47 | public function testExpectedRegistrationIdBehavior() 48 | { 49 | self::assertEquals($this->m->getRegistrationIds(), []); 50 | self::assertNotContains('registration_ids', $this->m->toJson()); 51 | $this->m->setRegistrationIds($this->validRegistrationIds); 52 | self::assertEquals($this->m->getRegistrationIds(), $this->validRegistrationIds); 53 | foreach ($this->validRegistrationIds as $id) { 54 | $this->m->addRegistrationId($id); 55 | } 56 | self::assertEquals($this->m->getRegistrationIds(), $this->validRegistrationIds); 57 | self::assertContains('registration_ids', $this->m->toJson()); 58 | $this->m->clearRegistrationIds(); 59 | self::assertEquals($this->m->getRegistrationIds(), []); 60 | self::assertNotContains('registration_ids', $this->m->toJson()); 61 | $this->m->addRegistrationId('1029384756'); 62 | self::assertEquals($this->m->getRegistrationIds(), ['1029384756']); 63 | self::assertContains('registration_ids', $this->m->toJson()); 64 | } 65 | 66 | public function testInvalidRegistrationIdThrowsException() 67 | { 68 | $this->expectException(\InvalidArgumentException::class); 69 | $this->m->addRegistrationId(['1234']); 70 | } 71 | 72 | public function testExpectedCollapseKeyBehavior() 73 | { 74 | self::assertEquals($this->m->getCollapseKey(), null); 75 | self::assertNotContains('collapse_key', $this->m->toJson()); 76 | $this->m->setCollapseKey('my collapse key'); 77 | self::assertEquals($this->m->getCollapseKey(), 'my collapse key'); 78 | self::assertContains('collapse_key', $this->m->toJson()); 79 | $this->m->setCollapseKey(null); 80 | self::assertEquals($this->m->getCollapseKey(), null); 81 | self::assertNotContains('collapse_key', $this->m->toJson()); 82 | } 83 | 84 | public function testInvalidCollapseKeyThrowsException() 85 | { 86 | $this->expectException(\InvalidArgumentException::class); 87 | $this->m->setCollapseKey(['1234']); 88 | } 89 | 90 | public function testExpectedDataBehavior() 91 | { 92 | self::assertEquals($this->m->getData(), []); 93 | self::assertNotContains('data', $this->m->toJson()); 94 | $this->m->setData($this->validData); 95 | self::assertEquals($this->m->getData(), $this->validData); 96 | self::assertContains('data', $this->m->toJson()); 97 | $this->m->clearData(); 98 | self::assertEquals($this->m->getData(), []); 99 | self::assertNotContains('data', $this->m->toJson()); 100 | $this->m->addData('mykey', 'myvalue'); 101 | self::assertEquals($this->m->getData(), ['mykey' => 'myvalue']); 102 | self::assertContains('data', $this->m->toJson()); 103 | } 104 | 105 | public function testExpectedNotificationBehavior() 106 | { 107 | $this->assertEquals($this->m->getNotification(), []); 108 | $this->assertNotContains('notification', $this->m->toJson()); 109 | $this->m->setNotification($this->validData); 110 | $this->assertEquals($this->m->getNotification(), $this->validData); 111 | $this->assertContains('notification', $this->m->toJson()); 112 | $this->m->clearNotification(); 113 | $this->assertEquals($this->m->getNotification(), []); 114 | $this->assertNotContains('notification', $this->m->toJson()); 115 | $this->m->addNotification('mykey', 'myvalue'); 116 | $this->assertEquals($this->m->getNotification(), ['mykey' => 'myvalue']); 117 | $this->assertContains('notification', $this->m->toJson()); 118 | } 119 | 120 | public function testInvalidDataThrowsException() 121 | { 122 | $this->expectException(\InvalidArgumentException::class); 123 | $this->m->addData(['1234'], 'value'); 124 | } 125 | 126 | public function testDuplicateDataKeyThrowsException() 127 | { 128 | $this->expectException(\RuntimeException::class); 129 | $this->m->setData($this->validData); 130 | $this->m->addData('key', 'value'); 131 | } 132 | 133 | public function testExpectedDelayWhileIdleBehavior() 134 | { 135 | self::assertEquals($this->m->getDelayWhileIdle(), false); 136 | self::assertNotContains('delay_while_idle', $this->m->toJson()); 137 | $this->m->setDelayWhileIdle(true); 138 | self::assertEquals($this->m->getDelayWhileIdle(), true); 139 | self::assertContains('delay_while_idle', $this->m->toJson()); 140 | $this->m->setDelayWhileIdle(false); 141 | self::assertEquals($this->m->getDelayWhileIdle(), false); 142 | self::assertNotContains('delay_while_idle', $this->m->toJson()); 143 | } 144 | 145 | public function testExpectedTimeToLiveBehavior() 146 | { 147 | self::assertEquals($this->m->getTimeToLive(), 2419200); 148 | self::assertNotContains('time_to_live', $this->m->toJson()); 149 | $this->m->setTimeToLive(12345); 150 | self::assertEquals($this->m->getTimeToLive(), 12345); 151 | self::assertContains('time_to_live', $this->m->toJson()); 152 | $this->m->setTimeToLive(2419200); 153 | self::assertEquals($this->m->getTimeToLive(), 2419200); 154 | self::assertNotContains('time_to_live', $this->m->toJson()); 155 | } 156 | 157 | public function testExpectedRestrictedPackageBehavior() 158 | { 159 | self::assertEquals($this->m->getRestrictedPackageName(), null); 160 | self::assertNotContains('restricted_package_name', $this->m->toJson()); 161 | $this->m->setRestrictedPackageName('my.package.name'); 162 | self::assertEquals($this->m->getRestrictedPackageName(), 'my.package.name'); 163 | self::assertContains('restricted_package_name', $this->m->toJson()); 164 | $this->m->setRestrictedPackageName(null); 165 | self::assertEquals($this->m->getRestrictedPackageName(), null); 166 | self::assertNotContains('restricted_package_name', $this->m->toJson()); 167 | } 168 | 169 | public function testInvalidRestrictedPackageThrowsException() 170 | { 171 | $this->expectException(\InvalidArgumentException::class); 172 | $this->m->setRestrictedPackageName(['1234']); 173 | } 174 | 175 | public function testExpectedDryRunBehavior() 176 | { 177 | self::assertEquals($this->m->getDryRun(), false); 178 | self::assertNotContains('dry_run', $this->m->toJson()); 179 | $this->m->setDryRun(true); 180 | self::assertEquals($this->m->getDryRun(), true); 181 | self::assertContains('dry_run', $this->m->toJson()); 182 | $this->m->setDryRun(false); 183 | self::assertEquals($this->m->getDryRun(), false); 184 | self::assertNotContains('dry_run', $this->m->toJson()); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /test/Gcm/ClientTest.php: -------------------------------------------------------------------------------- 1 | $id, 51 | 'success' => $success, 52 | 'failure' => $failure, 53 | 'canonical_ids' => $ids, 54 | 'results' => $results, 55 | ]); 56 | } 57 | 58 | public function setUp() 59 | { 60 | $this->httpClient = new HttpClient(); 61 | $this->httpAdapter = new Test(); 62 | $this->httpClient->setAdapter($this->httpAdapter); 63 | $this->gcmClient = new Client(); 64 | $this->gcmClient->setHttpClient($this->httpClient); 65 | $this->gcmClient->setApiKey('testing'); 66 | $this->message = new Message(); 67 | $this->message->addRegistrationId('testing'); 68 | $this->message->addData('testKey', 'testValue'); 69 | } 70 | 71 | public function testSetApiKeyThrowsExceptionOnNonString() 72 | { 73 | $this->expectException('InvalidArgumentException'); 74 | $this->gcmClient->setApiKey([]); 75 | } 76 | 77 | public function testSetApiKey() 78 | { 79 | $key = 'a-login-token'; 80 | $this->gcmClient->setApiKey($key); 81 | self::assertEquals($key, $this->gcmClient->getApiKey()); 82 | } 83 | 84 | public function testGetHttpClientReturnsDefault() 85 | { 86 | self::assertInstanceOf('Zend\Http\Client', (new Client())->getHttpClient()); 87 | } 88 | 89 | public function testSetHttpClient() 90 | { 91 | $client = new HttpClient(); 92 | $this->gcmClient->setHttpClient($client); 93 | self::assertEquals($client, $this->gcmClient->getHttpClient()); 94 | } 95 | 96 | public function testSendThrowsExceptionWhenServiceUnavailable() 97 | { 98 | $this->expectException('RuntimeException'); 99 | $this->httpAdapter->setResponse('HTTP/1.1 503 Service Unavailable'."\r\n\r\n"); 100 | $this->gcmClient->send($this->message); 101 | } 102 | 103 | public function testSendThrowsExceptionWhenServerUnavailable() 104 | { 105 | $this->expectException('RuntimeException'); 106 | $this->httpAdapter->setResponse('HTTP/1.1 500 Internal Server Error'."\r\n\r\n"); 107 | $this->gcmClient->send($this->message); 108 | } 109 | 110 | public function testSendThrowsExceptionWhenInvalidAuthToken() 111 | { 112 | $this->expectException('RuntimeException'); 113 | $this->httpAdapter->setResponse('HTTP/1.1 401 Unauthorized'."\r\n\r\n"); 114 | $this->gcmClient->send($this->message); 115 | } 116 | 117 | public function testSendThrowsExceptionWhenInvalidPayload() 118 | { 119 | $this->expectException('RuntimeException'); 120 | $this->httpAdapter->setResponse('HTTP/1.1 400 Bad Request'."\r\n\r\n"); 121 | $this->gcmClient->send($this->message); 122 | } 123 | 124 | public function testSendResultInvalidRegistrationId() 125 | { 126 | $body = $this->createJSONResponse(101, 0, 1, 0, [['error' => 'InvalidRegistration']]); 127 | $this->httpAdapter->setResponse( 128 | 'HTTP/1.1 200 OK'."\r\n". 129 | 'Context-Type: text/html'."\r\n\r\n". 130 | $body 131 | ); 132 | $response = $this->gcmClient->send($this->message); 133 | $result = $response->getResults(); 134 | $result = array_shift($result); 135 | self::assertEquals('InvalidRegistration', $result['error']); 136 | self::assertEquals(0, $response->getSuccessCount()); 137 | self::assertEquals(0, $response->getCanonicalCount()); 138 | self::assertEquals(1, $response->getFailureCount()); 139 | } 140 | 141 | public function testSendResultMismatchSenderId() 142 | { 143 | $body = $this->createJSONResponse(101, 0, 1, 0, [['error' => 'MismatchSenderId']]); 144 | $this->httpAdapter->setResponse( 145 | 'HTTP/1.1 200 OK'."\r\n". 146 | 'Context-Type: text/html'."\r\n\r\n". 147 | $body 148 | ); 149 | $response = $this->gcmClient->send($this->message); 150 | $result = $response->getResults(); 151 | $result = array_shift($result); 152 | self::assertEquals('MismatchSenderId', $result['error']); 153 | self::assertEquals(0, $response->getSuccessCount()); 154 | self::assertEquals(0, $response->getCanonicalCount()); 155 | self::assertEquals(1, $response->getFailureCount()); 156 | } 157 | 158 | public function testSendResultNotRegistered() 159 | { 160 | $body = $this->createJSONResponse(101, 0, 1, 0, [['error' => 'NotRegistered']]); 161 | $this->httpAdapter->setResponse( 162 | 'HTTP/1.1 200 OK'."\r\n". 163 | 'Context-Type: text/html'."\r\n\r\n". 164 | $body 165 | ); 166 | $response = $this->gcmClient->send($this->message); 167 | $result = $response->getResults(); 168 | $result = array_shift($result); 169 | self::assertEquals('NotRegistered', $result['error']); 170 | self::assertEquals(0, $response->getSuccessCount()); 171 | self::assertEquals(0, $response->getCanonicalCount()); 172 | self::assertEquals(1, $response->getFailureCount()); 173 | } 174 | 175 | public function testSendResultMessageTooBig() 176 | { 177 | $body = $this->createJSONResponse(101, 0, 1, 0, [['error' => 'MessageTooBig']]); 178 | $this->httpAdapter->setResponse( 179 | 'HTTP/1.1 200 OK'."\r\n". 180 | 'Context-Type: text/html'."\r\n\r\n". 181 | $body 182 | ); 183 | $response = $this->gcmClient->send($this->message); 184 | $result = $response->getResults(); 185 | $result = array_shift($result); 186 | self::assertEquals('MessageTooBig', $result['error']); 187 | self::assertEquals(0, $response->getSuccessCount()); 188 | self::assertEquals(0, $response->getCanonicalCount()); 189 | self::assertEquals(1, $response->getFailureCount()); 190 | } 191 | 192 | public function testSendResultSuccessful() 193 | { 194 | $body = $this->createJSONResponse(101, 1, 0, 0, [['message_id' => '1:2342']]); 195 | $this->httpAdapter->setResponse( 196 | 'HTTP/1.1 200 OK'."\r\n". 197 | 'Context-Type: text/html'."\r\n\r\n". 198 | $body 199 | ); 200 | $response = $this->gcmClient->send($this->message); 201 | $result = $response->getResults(); 202 | $result = array_shift($result); 203 | self::assertEquals('1:2342', $result['message_id']); 204 | self::assertEquals(1, $response->getSuccessCount()); 205 | self::assertEquals(0, $response->getCanonicalCount()); 206 | self::assertEquals(0, $response->getFailureCount()); 207 | } 208 | 209 | public function testSendResultSuccessfulWithRegistrationId() 210 | { 211 | $body = $this->createJSONResponse(101, 1, 0, 1, [['message_id' => '1:2342', 'registration_id' => 'testfoo']]); 212 | $this->httpAdapter->setResponse( 213 | 'HTTP/1.1 200 OK'."\r\n". 214 | 'Context-Type: text/html'."\r\n\r\n". 215 | $body 216 | ); 217 | $response = $this->gcmClient->send($this->message); 218 | $result = $response->getResults(); 219 | $result = array_shift($result); 220 | self::assertEquals('1:2342', $result['message_id']); 221 | self::assertEquals('testfoo', $result['registration_id']); 222 | self::assertEquals(1, $response->getSuccessCount()); 223 | self::assertEquals(1, $response->getCanonicalCount()); 224 | self::assertEquals(0, $response->getFailureCount()); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/Gcm/Message.php: -------------------------------------------------------------------------------- 1 | clearRegistrationIds(); 83 | foreach ($ids as $id) { 84 | $this->addRegistrationId($id); 85 | } 86 | 87 | return $this; 88 | } 89 | 90 | /** 91 | * Get Registration Ids. 92 | * 93 | * @return array 94 | */ 95 | public function getRegistrationIds() 96 | { 97 | return $this->registrationIds; 98 | } 99 | 100 | /** 101 | * Add Registration Ids. 102 | * 103 | * @param string $id 104 | * 105 | * @return Message 106 | * 107 | * @throws Exception\InvalidArgumentException 108 | */ 109 | public function addRegistrationId($id) 110 | { 111 | if (! is_string($id) || empty($id)) { 112 | throw new Exception\InvalidArgumentException('$id must be a non-empty string'); 113 | } 114 | if (! in_array($id, $this->registrationIds)) { 115 | $this->registrationIds[] = $id; 116 | } 117 | 118 | return $this; 119 | } 120 | 121 | /** 122 | * Clear Registration Ids. 123 | * 124 | * @return Message 125 | */ 126 | public function clearRegistrationIds() 127 | { 128 | $this->registrationIds = []; 129 | 130 | return $this; 131 | } 132 | 133 | /** 134 | * Get Collapse Key. 135 | * 136 | * @return string 137 | */ 138 | public function getCollapseKey() 139 | { 140 | return $this->collapseKey; 141 | } 142 | 143 | /** 144 | * Set Collapse Key. 145 | * 146 | * @param string $key 147 | * 148 | * @return Message 149 | * 150 | * @throws Exception\InvalidArgumentException 151 | */ 152 | public function setCollapseKey($key) 153 | { 154 | if (null !== $key && ! (is_string($key) && strlen($key) > 0)) { 155 | throw new Exception\InvalidArgumentException('$key must be null or a non-empty string'); 156 | } 157 | $this->collapseKey = $key; 158 | 159 | return $this; 160 | } 161 | 162 | /** 163 | * Get priority 164 | * 165 | * @return string 166 | */ 167 | public function getPriority() 168 | { 169 | return $this->priority; 170 | } 171 | 172 | /** 173 | * Set priority 174 | * 175 | * @param string $priority 176 | * @return Message 177 | * @throws Exception\InvalidArgumentException 178 | */ 179 | public function setPriority($priority) 180 | { 181 | if (! is_null($priority) && ! (is_string($priority) && strlen($priority) > 0)) { 182 | throw new Exception\InvalidArgumentException('$priority must be null or a non-empty string'); 183 | } 184 | $this->priority = $priority; 185 | return $this; 186 | } 187 | 188 | /** 189 | * Set Data 190 | * 191 | * @param array $data 192 | * 193 | * @throws \ZendService\Google\Exception\InvalidArgumentException 194 | * 195 | * @return Message 196 | */ 197 | public function setData(array $data) 198 | { 199 | $this->clearData(); 200 | foreach ($data as $k => $v) { 201 | $this->addData($k, $v); 202 | } 203 | 204 | return $this; 205 | } 206 | 207 | /** 208 | * Get Data. 209 | * 210 | * @return array 211 | */ 212 | public function getData() 213 | { 214 | return $this->data; 215 | } 216 | 217 | /** 218 | * Add Data. 219 | * 220 | * @param string $key 221 | * @param mixed $value 222 | * 223 | * @throws Exception\RuntimeException 224 | * @throws Exception\InvalidArgumentException 225 | * 226 | * @return Message 227 | */ 228 | public function addData($key, $value) 229 | { 230 | if (! is_string($key) || empty($key)) { 231 | throw new Exception\InvalidArgumentException('$key must be a non-empty string'); 232 | } 233 | if (array_key_exists($key, $this->data)) { 234 | throw new Exception\RuntimeException('$key conflicts with current set data'); 235 | } 236 | $this->data[$key] = $value; 237 | 238 | return $this; 239 | } 240 | 241 | /** 242 | * Clear Data. 243 | * 244 | * @return Message 245 | */ 246 | public function clearData() 247 | { 248 | $this->data = []; 249 | 250 | return $this; 251 | } 252 | 253 | /** 254 | * Set notification 255 | * 256 | * @param array $data 257 | * @return Message 258 | */ 259 | public function setNotification(array $data) 260 | { 261 | $this->clearNotification(); 262 | foreach ($data as $k => $v) { 263 | $this->addNotification($k, $v); 264 | } 265 | return $this; 266 | } 267 | 268 | /** 269 | * Get notification 270 | * 271 | * @return array 272 | */ 273 | public function getNotification() 274 | { 275 | return $this->notification; 276 | } 277 | 278 | /** 279 | * Add notification data 280 | * 281 | * @param string $key 282 | * @param mixed $value 283 | * @return Message 284 | * @throws Exception\InvalidArgumentException 285 | * @throws Exception\RuntimeException 286 | */ 287 | public function addNotification($key, $value) 288 | { 289 | if (! is_string($key) || empty($key)) { 290 | throw new Exception\InvalidArgumentException('$key must be a non-empty string'); 291 | } 292 | if (array_key_exists($key, $this->notification)) { 293 | throw new Exception\RuntimeException('$key conflicts with current set data'); 294 | } 295 | $this->notification[$key] = $value; 296 | return $this; 297 | } 298 | 299 | /** 300 | * Clear notification 301 | * 302 | * @return Message 303 | */ 304 | public function clearNotification() 305 | { 306 | $this->notification = []; 307 | 308 | return $this; 309 | } 310 | 311 | /** 312 | * Set Delay While Idle 313 | * 314 | * @param bool $delay 315 | * 316 | * @return Message 317 | */ 318 | public function setDelayWhileIdle($delay) 319 | { 320 | $this->delayWhileIdle = (bool) $delay; 321 | 322 | return $this; 323 | } 324 | 325 | /** 326 | * Get Delay While Idle. 327 | * 328 | * @return bool 329 | */ 330 | public function getDelayWhileIdle() 331 | { 332 | return $this->delayWhileIdle; 333 | } 334 | 335 | /** 336 | * Set Time to Live. 337 | * 338 | * @param int $ttl 339 | * 340 | * @return Message 341 | */ 342 | public function setTimeToLive($ttl) 343 | { 344 | $this->timeToLive = (int) $ttl; 345 | 346 | return $this; 347 | } 348 | 349 | /** 350 | * Get Time to Live. 351 | * 352 | * @return int 353 | */ 354 | public function getTimeToLive() 355 | { 356 | return $this->timeToLive; 357 | } 358 | 359 | /** 360 | * Set Restricted Package Name. 361 | * 362 | * @param string $name 363 | * 364 | * @return Message 365 | * 366 | * @throws Exception\InvalidArgumentException 367 | */ 368 | public function setRestrictedPackageName($name) 369 | { 370 | if (null !== $name && ! (is_string($name) && strlen($name) > 0)) { 371 | throw new Exception\InvalidArgumentException('$name must be null OR a non-empty string'); 372 | } 373 | $this->restrictedPackageName = $name; 374 | 375 | return $this; 376 | } 377 | 378 | /** 379 | * Get Restricted Package Name. 380 | * 381 | * @return string 382 | */ 383 | public function getRestrictedPackageName() 384 | { 385 | return $this->restrictedPackageName; 386 | } 387 | 388 | /** 389 | * Set Dry Run. 390 | * 391 | * @param bool $dryRun 392 | * 393 | * @return Message 394 | */ 395 | public function setDryRun($dryRun) 396 | { 397 | $this->dryRun = (bool) $dryRun; 398 | 399 | return $this; 400 | } 401 | 402 | /** 403 | * Get Dry Run. 404 | * 405 | * @return bool 406 | */ 407 | public function getDryRun() 408 | { 409 | return $this->dryRun; 410 | } 411 | 412 | /** 413 | * To JSON 414 | * Utility method to put the JSON into the 415 | * GCM proper format for sending the message. 416 | * 417 | * @return string 418 | */ 419 | public function toJson() 420 | { 421 | $json = []; 422 | if ($this->registrationIds) { 423 | $json['registration_ids'] = $this->registrationIds; 424 | } 425 | if ($this->collapseKey) { 426 | $json['collapse_key'] = $this->collapseKey; 427 | } 428 | if ($this->priority) { 429 | $json['priority'] = $this->priority; 430 | } 431 | if ($this->data) { 432 | $json['data'] = $this->data; 433 | } 434 | if ($this->notification) { 435 | $json['notification'] = $this->notification; 436 | } 437 | if ($this->delayWhileIdle) { 438 | $json['delay_while_idle'] = $this->delayWhileIdle; 439 | } 440 | if ($this->timeToLive != 2419200) { 441 | $json['time_to_live'] = $this->timeToLive; 442 | } 443 | if ($this->restrictedPackageName) { 444 | $json['restricted_package_name'] = $this->restrictedPackageName; 445 | } 446 | if ($this->dryRun) { 447 | $json['dry_run'] = $this->dryRun; 448 | } 449 | 450 | return Json::encode($json); 451 | } 452 | } 453 | --------------------------------------------------------------------------------