Response::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
c 0
b 0
f 0
rs 10
cc 1
nc 1
nop 4
1
<?php
2
3
/*
4
 * This file is part of the Pushok package.
5
 *
6
 * (c) Arthur Edamov <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Pushok;
13
14
/**
15
 * Class Response
16
 * @package Pushok
17
 *
18
 * @see http://bit.ly/communicating-with-apns
19
 */
20
class Response implements ApnsResponseInterface
21
{
22
    const APNS_SUCCESS = 200;
23
    const APNS_BAD_REQUEST = 400;
24
    const APNS_AUTH_PROVIDER_ERROR = 403;
25
    const APNS_METHOD_NOT_ALLOWED = 405;
26
    const APNS_NOT_ACTIVE_DEVICE_TOKEN_ = 410;
27
    const APNS_PAYLOAD_TOO_LARGE = 413;
28
    const APNS_TOO_MANY_REQUESTS = 429;
29
    const APNS_SERVER_ERROR = 500;
30
    const APNS_SERVER_UNAVAILABLE = 503;
31
32
    /**
33
     * Reason phrases by status code.
34
     *
35
     * @var array
36
     */
37
    private static $reasonPhrases = [
38
        200 => 'Success.',
39
        400 => 'Bad request.',
40
        403 => 'There was an error with the certificate or with the provider authentication token.',
41
        405 => 'The request used a bad :method value. Only POST requests are supported.',
42
        410 => 'The device token is no longer active for the topic.',
43
        413 => 'The notification payload was too large.',
44
        429 => 'The server received too many requests for the same device token.',
45
        500 => 'Internal server error.',
46
        503 => 'The server is shutting down and unavailable.',
47
    ];
48
49
    /**
50
     * Error reasons by status code.
51
     *
52
     * @var array
53
     */
54
    private static $errorReasons = [
55
        400 => [
56
            'BadCollapseId' => 'The collapse identifier exceeds the maximum allowed size',
57
            'BadDeviceToken' => 'The specified device token was bad.' .
58
                ' Verify that the request contains a valid token and that the token matches the environment',
59
            'BadExpirationDate' => 'The apns-expiration value is bad',
60
            'BadMessageId' => 'The apns-id value is bad',
61
            'BadPriority' => 'The apns-priority value is bad',
62
            'BadTopic' => 'The apns-topic was invalid',
63
            'DeviceTokenNotForTopic' => 'The device token does not match the specified topic',
64
            'DuplicateHeaders' => 'One or more headers were repeated',
65
            'IdleTimeout' => 'Idle time out',
66
            'MissingDeviceToken' => 'The device token is not specified in the request :path.' .
67
                ' Verify that the :path header contains the device token',
68
            'MissingTopic' => 'The apns-topic header of the request was not specified and was required.' .
69
                ' The apns-topic header is mandatory' .
70
                ' when the client is connected using a certificate that supports multiple topics',
71
            'PayloadEmpty' => 'The message payload was empty',
72
            'TopicDisallowed' => 'Pushing to this topic is not allowed',
73
        ],
74
        403 => [
75
            'BadCertificate' => 'The certificate was bad',
76
            'BadCertificateEnvironment' => 'The client certificate was for the wrong environment',
77
            'ExpiredProviderToken' => 'The provider token is stale and a new token should be generated',
78
            'Forbidden' => 'The specified action is not allowed',
79
            'InvalidProviderToken' => 'The provider token is not valid or the token signature could not be verified',
80
            'MissingProviderToken' => 'No provider certificate was used to connect to APNs' .
81
                ' and Authorization header was missing or no provider token was specified',
82
        ],
83
        404 => [
84
            'BadPath' => 'The request contained a bad :path value'
85
        ],
86
        405 => [
87
            'MethodNotAllowed' => 'The specified :method was not POST'
88
        ],
89
        410 => [
90
            'Unregistered' => 'The device token is inactive for the specified topic.'
91
        ],
92
        413 => [
93
            'PayloadTooLarge' => 'The message payload was too large.' .
94
                ' See The Remote Notification Payload for details on maximum payload size'
95
        ],
96
        429 => [
97
            'TooManyRequests' => 'Too many requests were made consecutively to the same device token'
98
        ],
99
        500 => [
100
            'InternalServerError' => 'An internal server error occurred'
101
        ],
102
        503 => [
103
            'ServiceUnavailable' => 'The service is unavailable',
104
            'Shutdown' => 'The server is shutting down',
105
        ],
106
    ];
107
108
    /**
109
     * APNs Id.
110
     *
111
     * @var string|null
112
     */
113
    private $apnsId;
114
115
    /**
116
     * Device token.
117
     *
118
     * @var string|null
119
     */
120
    private $deviceToken;
121
122
    /**
123
     * Response status code.
124
     *
125
     * @var int
126
     */
127
    private $statusCode;
128
129
    /**
130
     * Error reason.
131
     *
132
     * @var string
133
     */
134
    private $errorReason;
135
136
    /**
137
     * Timestamp for a 410 error
138
     *
139
     * @var string
140
     */
141
    private $error410Timestamp;
142
143
    /**
144
     * Response constructor.
145
     *
146
     * @param int $statusCode
147
     * @param string $headers
148
     * @param string $body
149
     * @param string $deviceToken
150
     */
151
    public function __construct(int $statusCode, string $headers, string $body, string $deviceToken = null)
152
    {
153
        $this->statusCode = $statusCode;
154
        $this->apnsId = self::fetchApnsId($headers);
155
        $this->errorReason = self::fetchErrorReason($body);
156
        $this->error410Timestamp = self::fetch410Timestamp($statusCode, $body);
157
        $this->deviceToken = $deviceToken;
158
    }
159
160
    /**
161
     * Fetch APNs Id from response headers.
162
     *
163
     * @param string $headers
164
     * @return string
165
     */
166
    private static function fetchApnsId(string $headers): string
167
    {
168
        $data = explode("\n", trim($headers));
169
170
        foreach ($data as $part) {
171
            $middle = explode(":", $part);
172
173
            if ($middle[0] !== 'apns-id') {
174
                continue;
175
            }
176
177
            return trim($middle[1]);
178
        }
179
180
        return '';
181
    }
182
183
    /**
184
     * Fetch error reason from response body.
185
     *
186
     * @param string $body
187
     * @return string
188
     */
189
    private static function fetchErrorReason(string $body): string
190
    {
191
        return json_decode($body, true)['reason'] ?? '';
192
    }
193
194
    /**
195
     * Fetch timestamp for a 410 error.
196
     * https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns#2947616
197
     *
198
     * @param int $statusCode
199
     * @param string $body
200
     * @return string
201
     */
202
    private static function fetch410Timestamp(int $statusCode, string $body): string
203
    {
204
        if ($statusCode === 410) {
205
            return (string)(json_decode($body, true)['timestamp'] ?? '');
206
        }
207
        return '';
208
    }
209
210
    /**
211
     * Get APNs Id
212
     *
213
     * @return string|null
214
     */
215
    public function getApnsId()
216
    {
217
        return $this->apnsId;
218
    }
219
220
    /**
221
     * Get device token
222
     *
223
     * @return string|null
224
     */
225
    public function getDeviceToken()
226
    {
227
        return $this->deviceToken;
228
    }
229
230
    /**
231
     * Get status code.
232
     *
233
     * @return int
234
     */
235
    public function getStatusCode(): int
236
    {
237
        return $this->statusCode;
238
    }
239
240
    /**
241
     * Get reason phrase.
242
     *
243
     * @return string
244
     */
245
    public function getReasonPhrase(): string
246
    {
247
        return self::$reasonPhrases[$this->statusCode] ?? '';
248
    }
249
250
    /**
251
     * Get error reason.
252
     *
253
     * @return string
254
     */
255
    public function getErrorReason(): string
256
    {
257
        return $this->errorReason;
258
    }
259
260
    /**
261
     * Get error description.
262
     *
263
     * @return string
264
     */
265
    public function getErrorDescription(): string
266
    {
267
        if (isset(self::$errorReasons[$this->statusCode][$this->errorReason])) {
268
            return self::$errorReasons[$this->statusCode][$this->errorReason];
269
        }
270
271
        return '';
272
    }
273
274
    /**
275
     * Get timestamp for a status 410 error
276
     *
277
     * @return string
278
     */
279
    public function get410Timestamp(): string
280
    {
281
        return $this->error410Timestamp;
282
    }
283
}
284