GcmAdapter::push()   C
last analyzed

Complexity

Conditions 7
Paths 28

Size

Total Lines 47
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 47
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 27
nc 28
nop 1
1
<?php
2
/**
3
 * Push notification services abstraction (http://github.com/juliangut/tify)
4
 *
5
 * @link https://github.com/juliangut/tify for the canonical source repository
6
 *
7
 * @license https://github.com/juliangut/tify/blob/master/LICENSE
8
 */
9
10
namespace Jgut\Tify\Adapter\Gcm;
11
12
use Jgut\Tify\Adapter\AbstractAdapter;
13
use Jgut\Tify\Adapter\PushAdapter;
14
use Jgut\Tify\Notification;
15
use Jgut\Tify\Receiver\GcmReceiver;
16
use Jgut\Tify\Result;
17
use ZendService\Google\Exception\RuntimeException as GcmRuntimeException;
18
19
/**
20
 * Class GcmAdapter
21
 */
22
class GcmAdapter extends AbstractAdapter implements PushAdapter
23
{
24
    const RESPONSE_OK = 'OK';
25
    const RESPONSE_MISSING_REGISTRATION = 'MissingRegistration';
26
    const RESPONSE_INVALID_REGISTRATION = 'InvalidRegistration';
27
    const RESPONSE_NOT_REGISTERED = 'NotRegistered';
28
    const RESPONSE_INVALID_PACKAGE_NAME = 'InvalidPackageName';
29
    const RESPONSE_MISMATCH_SENDER_ID = 'MismatchSenderId';
30
    const RESPONSE_MESSAGE_TOO_BIG = 'MessageTooBig';
31
    const RESPONSE_INVALID_DATA_KEY = 'InvalidDataKey';
32
    const RESPONSE_INVALID_TTL = 'InvalidTtl';
33
    const RESPONSE_TIMEOUT = 'Timeout';
34
    const RESPONSE_INTERNAL_SERVER_ERROR = 'InternalServerError';
35
    const RESPONSE_DEVICE_MESSAGE_RATE_EXCEEDED = 'DeviceMessageRateExceeded';
36
    const RESPONSE_TOPIC_MESSAGE_RATE_EXCEEDED = 'TopicsMessageRateExceeded';
37
    const RESPONSE_SERVER_UNAVAILABLE = 'ServerUnavailable';
38
    const RESPONSE_AUTHENTICATION_ERROR = 'AuthenticationError';
39
    const RESPONSE_INVALID_MESSAGE = 'InvalidMessage';
40
    const RESPONSE_BADLY_FORMATTED_RESPONSE = 'BadlyFormattedResponse';
41
    const RESPONSE_UNKNOWN_ERROR = 'Unknown';
42
43
    /**
44
     * Status codes mapping.
45
     *
46
     * @var array
47
     */
48
    protected static $statusCodes = [
49
        self::RESPONSE_OK => Result::STATUS_SUCCESS,
50
51
        self::RESPONSE_MISSING_REGISTRATION => Result::STATUS_INVALID_MESSAGE,
52
        self::RESPONSE_INVALID_PACKAGE_NAME => Result::STATUS_INVALID_MESSAGE,
53
        self::RESPONSE_MISMATCH_SENDER_ID => Result::STATUS_INVALID_MESSAGE,
54
        self::RESPONSE_MESSAGE_TOO_BIG => Result::STATUS_INVALID_MESSAGE,
55
        self::RESPONSE_INVALID_DATA_KEY => Result::STATUS_INVALID_MESSAGE,
56
        self::RESPONSE_INVALID_TTL => Result::STATUS_INVALID_MESSAGE,
57
        self::RESPONSE_INVALID_MESSAGE => Result::STATUS_INVALID_MESSAGE,
58
59
        self::RESPONSE_INVALID_REGISTRATION => Result::STATUS_INVALID_DEVICE,
60
        self::RESPONSE_NOT_REGISTERED => Result::STATUS_INVALID_DEVICE,
61
62
        self::RESPONSE_DEVICE_MESSAGE_RATE_EXCEEDED => Result::STATUS_RATE_ERROR,
63
        self::RESPONSE_TOPIC_MESSAGE_RATE_EXCEEDED => Result::STATUS_RATE_ERROR,
64
65
        self::RESPONSE_AUTHENTICATION_ERROR => Result::STATUS_AUTH_ERROR,
66
67
        self::RESPONSE_TIMEOUT => Result::STATUS_SERVER_ERROR,
68
        self::RESPONSE_INTERNAL_SERVER_ERROR => Result::STATUS_SERVER_ERROR,
69
        self::RESPONSE_SERVER_UNAVAILABLE => Result::STATUS_SERVER_ERROR,
70
        self::RESPONSE_BADLY_FORMATTED_RESPONSE => Result::STATUS_SERVER_ERROR,
71
72
        self::RESPONSE_UNKNOWN_ERROR => Result::STATUS_UNKNOWN_ERROR,
73
    ];
74
75
    /**
76
     * Status messages mapping.
77
     *
78
     * @see https://developers.google.com/cloud-messaging/http-server-ref
79
     *
80
     * @var array
81
     */
82
    protected static $statusMessages = [
83
        self::RESPONSE_OK => 'OK',
84
85
        self::RESPONSE_MISSING_REGISTRATION => 'Missing Registration Token',
86
        self::RESPONSE_INVALID_PACKAGE_NAME => 'Invalid Package Name',
87
        self::RESPONSE_MISMATCH_SENDER_ID => 'Mismatched Sender',
88
        self::RESPONSE_MESSAGE_TOO_BIG => 'Message Too Big',
89
        self::RESPONSE_INVALID_DATA_KEY => 'Invalid Data Key',
90
        self::RESPONSE_INVALID_TTL => 'Invalid Time to Live',
91
        self::RESPONSE_INVALID_MESSAGE => 'Invalid message',
92
93
        self::RESPONSE_INVALID_REGISTRATION => 'Invalid Registration Token',
94
        self::RESPONSE_NOT_REGISTERED => 'Unregistered Device',
95
96
        self::RESPONSE_DEVICE_MESSAGE_RATE_EXCEEDED => 'Device Message Rate Exceeded',
97
        self::RESPONSE_TOPIC_MESSAGE_RATE_EXCEEDED => 'Topics Message Rate Exceeded',
98
99
        self::RESPONSE_AUTHENTICATION_ERROR => 'Authentication Error',
100
101
        self::RESPONSE_TIMEOUT => 'Timeout',
102
        self::RESPONSE_INTERNAL_SERVER_ERROR => 'Internal Server Error',
103
        self::RESPONSE_SERVER_UNAVAILABLE => 'Server Unavailable',
104
        self::RESPONSE_BADLY_FORMATTED_RESPONSE => 'Bad Formatted Response',
105
106
        self::RESPONSE_UNKNOWN_ERROR => 'Unknown Error',
107
    ];
108
109
    /**
110
     * GCM service builder.
111
     *
112
     * @var \Jgut\Tify\Adapter\Gcm\GcmBuilder
113
     */
114
    protected $builder;
115
116
    /**
117
     * @var \ZendService\Google\Gcm\Client
118
     */
119
    protected $pushClient;
120
121
    /**
122
     * @param array                                  $parameters
123
     * @param \Jgut\Tify\Adapter\Gcm\GcmBuilder|null $builder
124
     *
125
     * @throws \Jgut\Tify\Exception\AdapterException
126
     */
127
    public function __construct(array $parameters = [], GcmBuilder $builder = null)
128
    {
129
        parent::__construct($parameters, false);
130
131
        // @codeCoverageIgnoreStart
132
        if ($builder === null) {
133
            $builder = new GcmBuilder;
134
        }
135
        // @codeCoverageIgnoreEnd
136
        $this->builder = $builder;
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     *
142
     * @throws \InvalidArgumentException
143
     * @throws \ZendService\Google\Exception\InvalidArgumentException
144
     * @throws \ZendService\Google\Exception\RuntimeException
145
     */
146
    public function push(Notification $notification)
147
    {
148
        $client = $this->getPushClient();
149
150
        $pushResults = [];
151
152
        /* @var \ZendService\Google\Gcm\Message $message */
153
        foreach ($this->getPushMessages($notification) as $message) {
154
            $date = new \DateTime('now', new \DateTimeZone('UTC'));
155
156
            try {
157
                $pushResponses = $client->send($message)->getResults();
158
159
                foreach ($message->getRegistrationIds() as $token) {
160
                    $statusCode = self::RESPONSE_OK;
161
162
                    if (!array_key_exists($token, $pushResponses)) {
163
                        $statusCode = self::RESPONSE_UNKNOWN_ERROR;
164
                    } elseif (array_key_exists('error', $pushResponses[$token])) {
165
                        $statusCode = $pushResponses[$token]['error'];
166
                    }
167
168
                    $pushResults[] = new Result(
169
                        $token,
170
                        $date,
171
                        self::$statusCodes[$statusCode],
172
                        self::$statusMessages[$statusCode]
173
                    );
174
                }
175
            // @codeCoverageIgnoreStart
176
            } catch (GcmRuntimeException $exception) {
177
                $statusCode = $this->getErrorCodeFromException($exception);
178
179
                foreach ($message->getRegistrationIds() as $token) {
180
                    $pushResults[] = new Result(
181
                        $token,
182
                        $date,
183
                        self::$statusCodes[$statusCode],
184
                        self::$statusMessages[$statusCode]
185
                    );
186
                }
187
            }
188
            // @codeCoverageIgnoreEnd
189
        }
190
191
        return $pushResults;
192
    }
193
194
    /**
195
     * Get opened client.
196
     *
197
     * @return \ZendService\Google\Gcm\Client
198
     */
199
    protected function getPushClient()
200
    {
201
        if ($this->pushClient === null) {
202
            $this->pushClient = $this->builder->buildPushClient($this->getParameter('api_key'));
203
        }
204
205
        return $this->pushClient;
206
    }
207
208
    /**
209
     * Get service formatted push messages.
210
     *
211
     * @param \Jgut\Tify\Notification $notification
212
     *
213
     * @throws \ZendService\Google\Exception\InvalidArgumentException
214
     * @throws \ZendService\Google\Exception\RuntimeException
215
     *
216
     * @return \Generator
217
     */
218
    protected function getPushMessages(Notification $notification)
219
    {
220
        foreach (array_chunk($notification->getReceivers(), 100) as $receivers) {
221
            $tokens = array_map(
222
                function ($receiver) {
223
                    return $receiver instanceof GcmReceiver ? $receiver->getToken() : null;
224
                },
225
                $receivers
226
            );
227
228
            yield $this->builder->buildPushMessage(array_filter($tokens), $notification);
229
        }
230
    }
231
232
    /**
233
     * Extract error code from exception.
234
     *
235
     * @param \ZendService\Google\Exception\RuntimeException $exception
236
     *
237
     * @return string
238
     */
239
    protected function getErrorCodeFromException(GcmRuntimeException $exception)
240
    {
241
        $message = $exception->getMessage();
242
243
        if (preg_match('/^500 Internal Server Error/', $message)) {
244
            return self::RESPONSE_INTERNAL_SERVER_ERROR;
245
        }
246
247
        if (preg_match('/^503 Server Unavailable/', $message)) {
248
            return self::RESPONSE_SERVER_UNAVAILABLE;
249
        }
250
251
        if (preg_match('/^400 Bad Request/', $message)) {
252
            return self::RESPONSE_INVALID_MESSAGE;
253
        }
254
255
        if (preg_match('/^401 Forbidden/', $message)) {
256
            return self::RESPONSE_AUTHENTICATION_ERROR;
257
        }
258
259
        if (preg_match('/^Response body did not contain a valid JSON response$/', $message)) {
260
            return self::RESPONSE_BADLY_FORMATTED_RESPONSE;
261
        }
262
263
        return self::RESPONSE_UNKNOWN_ERROR;
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269
    protected function getDefinedParameters()
270
    {
271
        return ['api_key'];
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277
    protected function getRequiredParameters()
278
    {
279
        return ['api_key'];
280
    }
281
}
282