Completed
Branch 2.x (c99d86)
by Julián
08:01
created

GcmAdapter::getDefinedParameters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/*
4
 * Unified push notification services abstraction (http://github.com/juliangut/tify).
5
 *
6
 * @license BSD-3-Clause
7
 * @link https://github.com/juliangut/tify
8
 * @author Julián Gutiérrez <[email protected]>
9
 */
10
11
namespace Jgut\Tify\Adapter;
12
13
use Jgut\Tify\Adapter\Gcm\DefaultFactory;
14
use Jgut\Tify\Adapter\Gcm\Factory;
15
use Jgut\Tify\Notification;
16
use Jgut\Tify\Receiver\GcmReceiver;
17
use Jgut\Tify\Result;
18
use ZendService\Google\Exception\RuntimeException as GcmRuntimeException;
19
20
/**
21
 * GCM service adapter.
22
 */
23
class GcmAdapter extends AbstractAdapter implements PushAdapter
24
{
25
    const PARAMETER_API_KEY = 'api_key';
26
27
    const RESPONSE_OK = 'OK';
28
    const RESPONSE_MISSING_REGISTRATION = 'MissingRegistration';
29
    const RESPONSE_INVALID_REGISTRATION = 'InvalidRegistration';
30
    const RESPONSE_NOT_REGISTERED = 'NotRegistered';
31
    const RESPONSE_INVALID_PACKAGE_NAME = 'InvalidPackageName';
32
    const RESPONSE_MISMATCH_SENDER_ID = 'MismatchSenderId';
33
    const RESPONSE_MESSAGE_TOO_BIG = 'MessageTooBig';
34
    const RESPONSE_INVALID_DATA_KEY = 'InvalidDataKey';
35
    const RESPONSE_INVALID_TTL = 'InvalidTtl';
36
    const RESPONSE_TIMEOUT = 'Timeout';
37
    const RESPONSE_INTERNAL_SERVER_ERROR = 'InternalServerError';
38
    const RESPONSE_DEVICE_MESSAGE_RATE_EXCEEDED = 'DeviceMessageRateExceeded';
39
    const RESPONSE_TOPIC_MESSAGE_RATE_EXCEEDED = 'TopicsMessageRateExceeded';
40
    const RESPONSE_SERVER_UNAVAILABLE = 'ServerUnavailable';
41
    const RESPONSE_AUTHENTICATION_ERROR = 'AuthenticationError';
42
    const RESPONSE_INVALID_MESSAGE = 'InvalidMessage';
43
    const RESPONSE_BADLY_FORMATTED_RESPONSE = 'BadlyFormattedResponse';
44
    const RESPONSE_UNKNOWN_ERROR = 'Unknown';
45
46
    /**
47
     * Status codes mapping.
48
     *
49
     * @var array
50
     */
51
    protected static $statusCodeMap = [
52
        self::RESPONSE_OK => Result::STATUS_SUCCESS,
53
54
        self::RESPONSE_MISSING_REGISTRATION => Result::STATUS_INVALID_MESSAGE,
55
        self::RESPONSE_INVALID_PACKAGE_NAME => Result::STATUS_INVALID_MESSAGE,
56
        self::RESPONSE_MISMATCH_SENDER_ID => Result::STATUS_INVALID_MESSAGE,
57
        self::RESPONSE_MESSAGE_TOO_BIG => Result::STATUS_INVALID_MESSAGE,
58
        self::RESPONSE_INVALID_DATA_KEY => Result::STATUS_INVALID_MESSAGE,
59
        self::RESPONSE_INVALID_TTL => Result::STATUS_INVALID_MESSAGE,
60
        self::RESPONSE_INVALID_MESSAGE => Result::STATUS_INVALID_MESSAGE,
61
62
        self::RESPONSE_INVALID_REGISTRATION => Result::STATUS_INVALID_DEVICE,
63
        self::RESPONSE_NOT_REGISTERED => Result::STATUS_INVALID_DEVICE,
64
65
        self::RESPONSE_DEVICE_MESSAGE_RATE_EXCEEDED => Result::STATUS_RATE_ERROR,
66
        self::RESPONSE_TOPIC_MESSAGE_RATE_EXCEEDED => Result::STATUS_RATE_ERROR,
67
68
        self::RESPONSE_AUTHENTICATION_ERROR => Result::STATUS_AUTH_ERROR,
69
70
        self::RESPONSE_TIMEOUT => Result::STATUS_SERVER_ERROR,
71
        self::RESPONSE_INTERNAL_SERVER_ERROR => Result::STATUS_SERVER_ERROR,
72
        self::RESPONSE_SERVER_UNAVAILABLE => Result::STATUS_SERVER_ERROR,
73
        self::RESPONSE_BADLY_FORMATTED_RESPONSE => Result::STATUS_SERVER_ERROR,
74
75
        self::RESPONSE_UNKNOWN_ERROR => Result::STATUS_UNKNOWN_ERROR,
76
    ];
77
78
    /**
79
     * Status messages mapping.
80
     *
81
     * @see https://developers.google.com/cloud-messaging/http-server-ref
82
     *
83
     * @var array
84
     */
85
    protected static $statusMessageMap = [
86
        self::RESPONSE_OK => 'OK',
87
88
        self::RESPONSE_MISSING_REGISTRATION => 'Missing Registration Token',
89
        self::RESPONSE_INVALID_PACKAGE_NAME => 'Invalid Package Name',
90
        self::RESPONSE_MISMATCH_SENDER_ID => 'Mismatched Sender',
91
        self::RESPONSE_MESSAGE_TOO_BIG => 'Message Too Big',
92
        self::RESPONSE_INVALID_DATA_KEY => 'Invalid Data Key',
93
        self::RESPONSE_INVALID_TTL => 'Invalid Time to Live',
94
        self::RESPONSE_INVALID_MESSAGE => 'Invalid message',
95
96
        self::RESPONSE_INVALID_REGISTRATION => 'Invalid Registration Token',
97
        self::RESPONSE_NOT_REGISTERED => 'Unregistered Device',
98
99
        self::RESPONSE_DEVICE_MESSAGE_RATE_EXCEEDED => 'Device Message Rate Exceeded',
100
        self::RESPONSE_TOPIC_MESSAGE_RATE_EXCEEDED => 'Topics Message Rate Exceeded',
101
102
        self::RESPONSE_AUTHENTICATION_ERROR => 'Authentication Error',
103
104
        self::RESPONSE_TIMEOUT => 'Timeout',
105
        self::RESPONSE_INTERNAL_SERVER_ERROR => 'Internal Server Error',
106
        self::RESPONSE_SERVER_UNAVAILABLE => 'Server Unavailable',
107
        self::RESPONSE_BADLY_FORMATTED_RESPONSE => 'Bad Formatted Response',
108
109
        self::RESPONSE_UNKNOWN_ERROR => 'Unknown Error',
110
    ];
111
112
    /**
113
     * GCM service factory.
114
     *
115
     * @var Factory
116
     */
117
    protected $factory;
118
119
    /**
120
     * Push service client.
121
     *
122
     * @var \ZendService\Google\Gcm\Client
123
     */
124
    protected $pushClient;
125
126
    /**
127
     * GCM service adapter constructor.
128
     *
129
     * @param array   $parameters
130
     * @param bool    $sandbox
131
     * @param Factory $factory
0 ignored issues
show
Documentation introduced by
Should the type for parameter $factory not be null|Factory?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
132
     *
133
     * @throws \Jgut\Tify\Exception\AdapterException
134
     */
135
    public function __construct(array $parameters = [], $sandbox = false, Factory $factory = null)
136
    {
137
        parent::__construct($parameters, $sandbox);
138
139
        // @codeCoverageIgnoreStart
140
        if ($factory === null) {
141
            $factory = new DefaultFactory;
142
        }
143
        // @codeCoverageIgnoreEnd
144
145
        $this->factory = $factory;
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     *
151
     * @throws \InvalidArgumentException
152
     * @throws \ZendService\Google\Exception\InvalidArgumentException
153
     * @throws \ZendService\Google\Exception\RuntimeException
154
     */
155
    public function push(Notification $notification)
156
    {
157
        $client = $this->getPushClient();
158
159
        $pushResults = [];
160
161
        foreach ($this->getPushMessage($notification) as $pushMessage) {
162
            $date = new \DateTime('now', new \DateTimeZone('UTC'));
163
164
            try {
165
                $pushResponses = $client->send($pushMessage)->getResults();
166
167
                foreach ($pushMessage->getRegistrationIds() as $token) {
168
                    $statusCode = self::RESPONSE_OK;
169
170
                    if (!array_key_exists($token, $pushResponses)) {
171
                        $statusCode = self::RESPONSE_UNKNOWN_ERROR;
172
                    } elseif (array_key_exists('error', $pushResponses[$token])) {
173
                        $statusCode = $pushResponses[$token]['error'];
174
                    }
175
176
                    $pushResults[] = new Result(
177
                        $token,
178
                        $date,
179
                        self::$statusCodeMap[$statusCode],
180
                        self::$statusMessageMap[$statusCode]
181
                    );
182
                }
183
            // @codeCoverageIgnoreStart
184
            } catch (GcmRuntimeException $exception) {
185
                $statusCode = $this->getErrorCodeFromException($exception);
186
187
                foreach ($pushMessage->getRegistrationIds() as $token) {
188
                    $pushResults[] = new Result(
189
                        $token,
190
                        $date,
191
                        self::$statusCodeMap[$statusCode],
192
                        self::$statusMessageMap[$statusCode]
193
                    );
194
                }
195
            }
196
            // @codeCoverageIgnoreEnd
197
        }
198
199
        return $pushResults;
200
    }
201
202
    /**
203
     * Get opened push client.
204
     *
205
     * @return \ZendService\Google\Gcm\Client
206
     */
207
    protected function getPushClient()
208
    {
209
        if ($this->pushClient === null) {
210
            $this->pushClient = $this->factory->buildPushClient($this->getParameter(static::PARAMETER_API_KEY));
211
        }
212
213
        return $this->pushClient;
214
    }
215
216
    /**
217
     * Get service formatted push message.
218
     *
219
     * @param Notification $notification
220
     *
221
     * @throws \ZendService\Google\Exception\InvalidArgumentException
222
     * @throws \ZendService\Google\Exception\RuntimeException
223
     *
224
     * @return \Generator<\ZendService\Google\Gcm\Message>
0 ignored issues
show
Documentation introduced by
The doc-type \Generator<\ZendService\Google\Gcm\Message> could not be parsed: Expected "|" or "end of type", but got "<" at position 10. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
225
     */
226
    protected function getPushMessage(Notification $notification)
227
    {
228
        foreach (array_chunk($notification->getReceivers(), 100) as $receivers) {
229
            $tokens = array_map(
230
                function ($receiver) {
231
                    return $receiver instanceof GcmReceiver ? $receiver->getToken() : null;
232
                },
233
                $receivers
234
            );
235
236
            $pushMessage = $this->factory->buildPushMessage(array_filter($tokens), $notification);
237
238
            if ($this->isSandbox()) {
239
                $pushMessage->setDryRun(true);
240
            }
241
242
            yield $pushMessage;
243
        }
244
    }
245
246
    /**
247
     * Extract error code from exception.
248
     *
249
     * @param GcmRuntimeException $exception
250
     *
251
     * @return string
252
     */
253
    protected function getErrorCodeFromException(GcmRuntimeException $exception)
254
    {
255
        $message = $exception->getMessage();
256
257
        if (preg_match('/^500 Internal Server Error/', $message)) {
258
            return static::RESPONSE_INTERNAL_SERVER_ERROR;
259
        }
260
261
        if (preg_match('/^503 Server Unavailable/', $message)) {
262
            return static::RESPONSE_SERVER_UNAVAILABLE;
263
        }
264
265
        if (preg_match('/^400 Bad Request/', $message)) {
266
            return static::RESPONSE_INVALID_MESSAGE;
267
        }
268
269
        if (preg_match('/^401 Forbidden/', $message)) {
270
            return static::RESPONSE_AUTHENTICATION_ERROR;
271
        }
272
273
        if (preg_match('/^Response body did not contain a valid JSON response$/', $message)) {
274
            return static::RESPONSE_BADLY_FORMATTED_RESPONSE;
275
        }
276
277
        return static::RESPONSE_UNKNOWN_ERROR;
278
    }
279
280
    /**
281
     * {@inheritdoc}
282
     */
283
    protected function getDefinedParameters()
284
    {
285
        return [static::PARAMETER_API_KEY];
286
    }
287
288
    /**
289
     * {@inheritdoc}
290
     */
291
    protected function getRequiredParameters()
292
    {
293
        return [static::PARAMETER_API_KEY];
294
    }
295
}
296