Completed
Push — master ( 8d719e...0f62ec )
by Arthur
08:07
created

Response::fetchApnsId()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 1
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
     * Response status code.
117
     *
118
     * @var int
119
     */
120
    private $statusCode;
121
122
    /**
123
     * Error reason.
124
     *
125
     * @var string
126
     */
127
    private $errorReason;
128
129
    /**
130
     * Response constructor.
131
     *
132
     * @param int $statusCode
133
     * @param string $headers
134
     * @param string $body
135
     */
136
    public function __construct(int $statusCode, string $headers, string $body)
137
    {
138
        $this->statusCode = $statusCode;
139
        $this->apnsId = self::fetchApnsId($headers);
140
        $this->errorReason = self::fetchErrorReason($body);
141
    }
142
143
    /**
144
     * Fetch APNs Id from response headers.
145
     *
146
     * @param string $headers
147
     * @return string
148
     */
149
    private static function fetchApnsId(string $headers): string
150
    {
151
        $data = explode("\n", trim($headers));
152
153
        foreach ($data as $part) {
154
            $middle = explode(":", $part);
155
156
            if ($middle[0] !== 'apns-id') {
157
                continue;
158
            }
159
160
            return $middle[1];
161
        }
162
163
        return '';
164
    }
165
166
    /**
167
     * Fetch error reason from response body.
168
     *
169
     * @param string $body
170
     * @return string
171
     */
172
    private static function fetchErrorReason(string $body): string
173
    {
174
        return json_decode($body, true)['reason'] ?: '';
175
    }
176
177
    /**
178
     * Get APNs Id
179
     *
180
     * @return string|null
181
     */
182
    public function getApnsId()
183
    {
184
        return $this->apnsId;
185
    }
186
187
    /**
188
     * Get status code.
189
     *
190
     * @return int
191
     */
192
    public function getStatusCode(): int
193
    {
194
        return $this->statusCode;
195
    }
196
197
    /**
198
     * Get reason phrase.
199
     *
200
     * @return string
201
     */
202
    public function getReasonPhrase(): string
203
    {
204
        return self::$reasonPhrases[$this->statusCode] ?: '';
205
    }
206
207
    /**
208
     * Get error reason.
209
     *
210
     * @return string
211
     */
212
    public function getErrorReason(): string
213
    {
214
        return $this->errorReason;
215
    }
216
217
    /**
218
     * Get error description.
219
     *
220
     * @return string
221
     */
222
    public function getErrorDescription(): string
223
    {
224
        if (isset(self::$errorReasons[$this->statusCode][$this->errorReason])) {
225
            return self::$errorReasons[$this->statusCode][$this->errorReason];
226
        }
227
228
        return '';
229
    }
230
}
231