Completed
Push — master ( 020c5c...9dbce5 )
by Julián
08:49
created

GcmAdapter::getErrorCodeFromException()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 26
rs 8.439
cc 6
eloc 13
nc 6
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 RESULT_INTERNAL_SERVER_ERROR = 'InternalServerError';
25
    const RESULT_SERVER_UNAVAILABLE = 'ServerUnavailable';
26
    const RESULT_AUTHENTICATION_ERROR = 'AuthenticationError';
27
    const RESULT_INVALID_MESSAGE = 'InvalidMessage';
28
    const RESULT_BAD_FORMATTED_RESPONSE = 'BadFormattedResponse';
29
    const RESULT_UNKNOWN = 'Unknown';
30
31
    /**
32
     * Status codes mapping.
33
     *
34
     * @see https://developers.google.com/cloud-messaging/http-server-ref
35
     *
36
     * @var array
37
     */
38
    protected static $statusCodes = [
39
        'MissingRegistration' => 'Missing Registration Token',
40
        'InvalidRegistration' => 'Invalid Registration Token',
41
        'NotRegistered' => 'Unregistered Device',
42
        'InvalidPackageName' => 'Invalid Package Name',
43
        'MismatchSenderId' => 'Mismatched Sender',
44
        'MessageTooBig' => 'Message Too Big',
45
        'InvalidDataKey' => 'Invalid Data Key',
46
        'InvalidTtl' => 'Invalid Time to Live',
47
        'Unavailable' => 'Timeout',
48
        'InternalServerError' => 'Internal Server Error',
49
        'DeviceMessageRateExceeded' => 'Device Message Rate Exceeded',
50
        'TopicsMessageRateExceeded' => 'Topics Message Rate Exceeded',
51
        self::RESULT_INTERNAL_SERVER_ERROR => 'Internal Server Error',
52
        self::RESULT_SERVER_UNAVAILABLE => 'Server Unavailable',
53
        self::RESULT_AUTHENTICATION_ERROR => 'Authentication Error',
54
        self::RESULT_INVALID_MESSAGE => 'Invalid message',
55
        self::RESULT_BAD_FORMATTED_RESPONSE => 'Bad Formatted Response',
56
        self::RESULT_UNKNOWN => 'Unknown Error',
57
    ];
58
59
    /**
60
     * GCM service builder.
61
     *
62
     * @var \Jgut\Tify\Adapter\Gcm\GcmBuilder
63
     */
64
    protected $builder;
65
66
    /**
67
     * @var \ZendService\Google\Gcm\Client
68
     */
69
    protected $pushClient;
70
71
    /**
72
     * @param array                                  $parameters
73
     * @param \Jgut\Tify\Adapter\Gcm\GcmBuilder|null $builder
74
     *
75
     * @throws \Jgut\Tify\Exception\AdapterException
76
     */
77
    public function __construct(array $parameters = [], GcmBuilder $builder = null)
78
    {
79
        parent::__construct($parameters, false);
80
81
        // @codeCoverageIgnoreStart
82
        if ($builder === null) {
83
            $builder = new GcmBuilder;
84
        }
85
        // @codeCoverageIgnoreEnd
86
        $this->builder = $builder;
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     *
92
     * @throws \InvalidArgumentException
93
     * @throws \ZendService\Google\Exception\InvalidArgumentException
94
     * @throws \ZendService\Google\Exception\RuntimeException
95
     */
96
    public function push(Notification $notification)
97
    {
98
        $client = $this->getPushClient();
99
100
        $pushResults = [];
101
102
        /* @var \ZendService\Google\Gcm\Message $message */
103
        foreach ($this->getPushMessages($notification) as $message) {
104
            $date = new \DateTime('now', new \DateTimeZone('UTC'));
105
106
            try {
107
                $pushResponses = $client->send($message)->getResults();
108
109
                foreach ($message->getRegistrationIds() as $token) {
110
                    $pushResult = new Result($token, $date);
111
112
                    if (!array_key_exists($token, $pushResponses)
113
                        || array_key_exists('error', $pushResponses[$token])
114
                    ) {
115
                        $pushResult->setStatus(Result::STATUS_ERROR);
116
117
                        $errorCode = array_key_exists($token, $pushResponses)
118
                            ? $pushResponses[$token]['error']
119
                            : self::RESULT_UNKNOWN;
120
                        $pushResult->setStatusMessage(self::$statusCodes[$errorCode]);
121
                    }
122
123
                    $pushResults[] = $pushResult;
124
                }
125
            // @codeCoverageIgnoreStart
126
            } catch (GcmRuntimeException $exception) {
127
                $errorMessage = self::$statusCodes[$this->getErrorCodeFromException($exception)];
128
129
                foreach ($message->getRegistrationIds() as $token) {
130
                    $pushResults[] = new Result($token, $date, Result::STATUS_ERROR, $errorMessage);
131
                }
132
            }
133
            // @codeCoverageIgnoreEnd
134
        }
135
136
        return $pushResults;
137
    }
138
139
    /**
140
     * Get opened client.
141
     *
142
     * @return \ZendService\Google\Gcm\Client
143
     */
144
    protected function getPushClient()
145
    {
146
        if ($this->pushClient === null) {
147
            $this->pushClient = $this->builder->buildPushClient($this->getParameter('api_key'));
148
        }
149
150
        return $this->pushClient;
151
    }
152
153
    /**
154
     * Get service formatted push messages.
155
     *
156
     * @param \Jgut\Tify\Notification $notification
157
     *
158
     * @throws \ZendService\Google\Exception\InvalidArgumentException
159
     * @throws \ZendService\Google\Exception\RuntimeException
160
     *
161
     * @return \Generator
162
     */
163
    protected function getPushMessages(Notification $notification)
164
    {
165
        foreach (array_chunk($notification->getReceivers(), 100) as $receivers) {
166
            $tokens = array_map(
167
                function ($receiver) {
168
                    return $receiver instanceof GcmReceiver ? $receiver->getToken() : null;
169
                },
170
                $receivers
171
            );
172
173
            yield $this->builder->buildPushMessage(array_filter($tokens), $notification);
174
        }
175
    }
176
177
    /**
178
     * Extract error code from exception.
179
     *
180
     * @param \ZendService\Google\Exception\RuntimeException $exception
181
     *
182
     * @return string
183
     */
184
    protected function getErrorCodeFromException(GcmRuntimeException $exception)
185
    {
186
        $message = $exception->getMessage();
187
188
        if (preg_match('/^500 Internal Server Error/', $message)) {
189
            return self::RESULT_INTERNAL_SERVER_ERROR;
190
        }
191
192
        if (preg_match('/^503 Server Unavailable/', $message)) {
193
            return self::RESULT_SERVER_UNAVAILABLE;
194
        }
195
196
        if (preg_match('/^401 Forbidden/', $message)) {
197
            return self::RESULT_AUTHENTICATION_ERROR;
198
        }
199
200
        if (preg_match('/^400 Bad Request/', $message)) {
201
            return self::RESULT_INVALID_MESSAGE;
202
        }
203
204
        if (preg_match('/^Response body did not contain a valid JSON response$/', $message)) {
205
            return self::RESULT_BAD_FORMATTED_RESPONSE;
206
        }
207
208
        return self::RESULT_UNKNOWN;
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    protected function getDefinedParameters()
215
    {
216
        return ['api_key'];
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222
    protected function getRequiredParameters()
223
    {
224
        return ['api_key'];
225
    }
226
}
227