Completed
Push — master ( 0b1858...247883 )
by Artem
09:51
created

getSinchExceptionForInternalServerError()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 10
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 10
loc 10
rs 9.4286
cc 2
eloc 5
nc 2
nop 2
1
<?php
2
/*
3
 * This file is part of the FreshSinchBundle
4
 *
5
 * (c) Artem Genvald <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Fresh\SinchBundle\Service;
12
13
use Fresh\SinchBundle\Exception\BadRequest\SinchInvalidRequestException;
14
use Fresh\SinchBundle\Exception\BadRequest\SinchMissingParameterException;
15
use Fresh\SinchBundle\Exception\InternalServerError\SinchForbiddenRequestException;
16
use Fresh\SinchBundle\Exception\InternalServerError\SinchInternalErrorException;
17
use Fresh\SinchBundle\Exception\InternalServerError\SinchInvalidAuthorizationSchemeException;
18
use Fresh\SinchBundle\Exception\InternalServerError\SinchNoVerifiedPhoneNumberException;
19
use Fresh\SinchBundle\Exception\InternalServerError\SinchParameterValidationException;
20
use Fresh\SinchBundle\Exception\PaymentRequired\SinchPaymentRequiredException;
21
use Fresh\SinchBundle\Exception\Unauthorized\SinchIllegalAuthorizationHeaderException;
22
use Fresh\SinchBundle\Sms\SmsStatus;
23
use GuzzleHttp\Client;
24
use GuzzleHttp\Exception\ClientException;
25
use GuzzleHttp\Exception\GuzzleException;
26
use Symfony\Component\HttpFoundation\Response;
27
28
/**
29
 * SinchService
30
 *
31
 * @author Artem Genvald <[email protected]>
32
 */
33
class SinchService
34
{
35
    /**
36
     * @var Client $guzzleHTTPClient Guzzle HTTP client
37
     */
38
    private $guzzleHTTPClient;
39
40
    /**
41
     * @var string $host Host
42
     */
43
    private $host;
44
45
    /**
46
     * @var string $key Key
47
     */
48
    private $key;
49
50
    /**
51
     * @var string $secret Secret
52
     */
53
    private $secret;
54
55
    /**
56
     * @var string $from From
57
     */
58
    private $from;
59
60
    /**
61
     * Constructor
62
     *
63
     * @param string      $host   Host
64
     * @param string      $key    Key
65
     * @param string      $secret Secret
66
     * @param string|null $from   From
67
     */
68
    public function __construct($host, $key, $secret, $from = null)
69
    {
70
        $this->host   = $host;
71
        $this->key    = $key;
72
        $this->secret = $secret;
73
        $this->from   = $from;
74
75
        $this->guzzleHTTPClient = new Client([
76
            'base_uri' => rtrim($this->host, '/'),
77
        ]);
78
    }
79
80
    // region Public API
81
82
    /**
83
     * Send SMS
84
     *
85
     * @param string      $phoneNumber Phone number
86
     * @param string      $messageText Message text
87
     * @param string|null $from        From
88
     *
89
     * @return int Message ID
90
     *
91
     * @throws GuzzleException
92
     */
93
    public function sendSMS($phoneNumber, $messageText, $from = null)
94
    {
95
        $uri = '/v1/sms/'.$phoneNumber; // @todo validate phone number
96
97
        $body = [
98
            'auth'    => [$this->key, $this->secret],
99
            'headers' => ['X-Timestamp' => (new \DateTime('now'))->format('c')], // ISO 8601 date format
100
            'json'    => ['message' => $messageText],
101
        ];
102
103
        if (null !== $from) {
104
            $body['json']['from'] = $from;
105
        } elseif (null !== $this->from) {
106
            $body['json']['from'] = $this->from;
107
        }
108
109
        try {
110
            $response = $this->guzzleHTTPClient->post($uri, $body);
111
        } catch (ClientException $e) {
112
            throw $this->createAppropriateSinchException($e);
113
        }
114
115
        $messageId = null;
116
        if (Response::HTTP_OK === $response->getStatusCode() && $response->hasHeader('Content-Type') &&
117
            'application/json; charset=utf-8' === $response->getHeaderLine('Content-Type')
118
        ) {
119
            $content = $response->getBody()->getContents();
120
            $content = json_decode($content, true);
121
122
            if (isset($content['messageId']) && array_key_exists('messageId', $content)) {
123
                $messageId = $content['messageId'];
124
            }
125
        };
126
127
        return $messageId;
128
    }
129
130
    /**
131
     * Get status of sent SMS
132
     *
133
     * Available SMS statuses: Successful, Pending, Faulted, Unknown
134
     *
135
     * @param int $messageId Message ID
136
     *
137
     * @return string SMS status
138
     *
139
     * @throws GuzzleException
140
     */
141
    public function getStatusOfSMS($messageId)
142
    {
143
        $response = $this->sendRequestToCheckStatusOfSMS($messageId);
144
        $result = '';
145
146
        if (isset($response['status']) && array_key_exists('status', $response)) {
147
            $result = $response['status'];
148
        }
149
150
        return $result;
151
    }
152
153
    // endregion
154
155
    // region Check status helper methods
156
157
    /**
158
     * Returns true if SMS with some ID was sent successfully, otherwise returns false
159
     *
160
     * @param int $messageId Message ID
161
     *
162
     * @return bool True if SMS was sent successfully, otherwise - false
163
     */
164 View Code Duplication
    public function smsIsSentSuccessfully($messageId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
165
    {
166
        $response = $this->sendRequestToCheckStatusOfSMS($messageId);
167
168
        $result = false;
169
        if (isset($response['status']) && SmsStatus::SUCCESSFUL === $response['status']) {
170
            $result = true;
171
        }
172
173
        return $result;
174
    }
175
176
    /**
177
     * Returns true if SMS with some ID is still pending, otherwise returns false
178
     *
179
     * @param int $messageId Message ID
180
     *
181
     * @return bool True if SMS is still pending, otherwise - false
182
     */
183 View Code Duplication
    public function smsIsPending($messageId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
    {
185
        $response = $this->sendRequestToCheckStatusOfSMS($messageId);
186
187
        $result = false;
188
        if (isset($response['status']) && SmsStatus::PENDING === $response['status']) {
189
            $result = true;
190
        }
191
192
        return $result;
193
    }
194
195
    /**
196
     * Returns true if SMS with some ID was faulted, otherwise returns false
197
     *
198
     * @param int $messageId Message ID
199
     *
200
     * @return bool True if SMS was faulted, otherwise - false
201
     */
202 View Code Duplication
    public function smsIsFaulted($messageId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
    {
204
        $response = $this->sendRequestToCheckStatusOfSMS($messageId);
205
206
        $result = false;
207
        if (isset($response['status']) && SmsStatus::FAULTED === $response['status']) {
208
            $result = true;
209
        }
210
211
        return $result;
212
    }
213
214
    /**
215
     * Returns true if SMS with some ID in unknown status, otherwise returns false
216
     *
217
     * @param int $messageId Message ID
218
     *
219
     * @return bool True if SMS in unknown status, otherwise - false
220
     */
221 View Code Duplication
    public function smsInUnknownStatus($messageId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222
    {
223
        $response = $this->sendRequestToCheckStatusOfSMS($messageId);
224
225
        $result = false;
226
        if (isset($response['status']) && SmsStatus::UNKNOWN === $response['status']) {
227
            $result = true;
228
        }
229
230
        return $result;
231
    }
232
233
    // endregion
234
235
    // region Private functions
236
237
    /**
238
     * Send request to check status of SMS
239
     *
240
     * @param int $messageId Message ID
241
     *
242
     * @return array|null
243
     *
244
     * @throws SinchPaymentRequiredException When run out of money
245
     */
246
    private function sendRequestToCheckStatusOfSMS($messageId)
247
    {
248
        $uri = '/v1/message/status/'.$messageId;
249
250
        $body = [
251
            'auth'    => [$this->key, $this->secret],
252
            'headers' => ['X-Timestamp' => (new \DateTime('now'))->format('c')],
253
        ];
254
255
        try {
256
            $response = $this->guzzleHTTPClient->get($uri, $body);
257
        } catch (ClientException $e) {
258
            throw $this->createAppropriateSinchException($e);
259
        }
260
261
        $result = null;
262
263
        if (Response::HTTP_OK === $response->getStatusCode() && $response->hasHeader('Content-Type') &&
264
            'application/json; charset=utf-8' === $response->getHeaderLine('Content-Type')
265
        ) {
266
            $content = $response->getBody()->getContents();
267
            $result = json_decode($content, true);
268
        };
269
270
        return $result;
271
    }
272
273
    /**
274
     * Create appropriate Sinch exception
275
     *
276
     * @param ClientException $e Exception
277
     *
278
     * @return \Exception|\Fresh\SinchBundle\Exception\SinchException
279
     */
280
    private function createAppropriateSinchException(ClientException $e)
281
    {
282
        $response = json_decode($e->getResponse()->getBody()->getContents(), true);
283
        $responseStatusCode = $e->getCode();
284
285
        $errorCode    = (int) $response['errorCode'];
286
        $errorMessage = $response['message'];
287
288
        $exception = null;
289
290
        switch ($responseStatusCode) {
291
            case Response::HTTP_BAD_REQUEST:
292
                $exception = $this->getSinchExceptionForBadRequest($errorCode, $errorMessage);
293
                break;
294
            case Response::HTTP_UNAUTHORIZED:
295
                $exception = $this->getSinchExceptionForUnauthorized($errorCode, $errorMessage);
296
                break;
297
            case Response::HTTP_PAYMENT_REQUIRED:
298
                $exception = $this->getSinchExceptionForPaymentRequired($errorCode, $errorMessage);
299
                break;
300
            case Response::HTTP_FORBIDDEN:
301
                $exception = $this->getSinchExceptionForForbidden($errorCode, $errorMessage);
302
                break;
303
            case Response::HTTP_INTERNAL_SERVER_ERROR:
304
                $exception = $this->getSinchExceptionForInternalServerError($errorCode, $errorMessage);
305
                break;
306
        }
307
308
        if (null === $exception) {
309
            $exception = new \Exception('Unknown Sinch Error Code');
310
        }
311
312
        return $exception;
313
    }
314
315
    /**
316
     * Get Sinch exception for bad request
317
     *
318
     * @param int    $errorCode    Sinch error code
319
     * @param string $errorMessage Sinch error message
320
     *
321
     * @return \Fresh\SinchBundle\Exception\SinchException|null
322
     */
323 View Code Duplication
    private function getSinchExceptionForBadRequest($errorCode, $errorMessage)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
324
    {
325
        $exception = null;
326
327
        switch ($errorCode) {
328
            case SinchErrorCode::PARAMETER_VALIDATION:
329
                $exception = new SinchParameterValidationException($errorMessage);
330
                break;
331
            case SinchErrorCode::MISSING_PARAMETER:
332
                $exception = new SinchMissingParameterException($errorMessage);
333
                break;
334
            case SinchErrorCode::INVALID_REQUEST:
335
                $exception = new SinchInvalidRequestException($errorMessage);
336
                break;
337
        }
338
339
        return $exception;
340
    }
341
342
    /**
343
     * Get Sinch exception for unauthorized
344
     *
345
     * @param int    $errorCode    Sinch error code
346
     * @param string $errorMessage Sinch error message
347
     *
348
     * @return \Fresh\SinchBundle\Exception\SinchException|null
349
     */
350 View Code Duplication
    private function getSinchExceptionForUnauthorized($errorCode, $errorMessage)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
351
    {
352
        $exception = null;
353
354
        if (SinchErrorCode::ILLEGAL_AUTHORIZATION_HEADER === $errorCode) {
355
            $exception = new SinchIllegalAuthorizationHeaderException($errorMessage);
356
        }
357
358
        return $exception;
359
    }
360
361
    /**
362
     * Get Sinch exception for payment required
363
     *
364
     * Sinch returns 402 code when application run out of money
365
     *
366
     * @param int    $errorCode    Sinch error code
367
     * @param string $errorMessage Sinch error message
368
     *
369
     * @return \Fresh\SinchBundle\Exception\SinchException|null
370
     */
371 View Code Duplication
    private function getSinchExceptionForPaymentRequired($errorCode, $errorMessage)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
    {
373
        $exception = null;
374
375
        if (SinchErrorCode::THERE_IS_NOT_ENOUGH_FUNDS_TO_SEND_THE_MESSAGE === $errorCode) {
376
            $exception = new SinchPaymentRequiredException($errorMessage);
377
        }
378
379
        return $exception;
380
    }
381
382
    /**
383
     * Get Sinch exception for forbidden
384
     *
385
     * @param int    $errorCode    Sinch error code
386
     * @param string $errorMessage Sinch error message
387
     *
388
     * @return \Fresh\SinchBundle\Exception\SinchException|null
389
     */
390 View Code Duplication
    private function getSinchExceptionForForbidden($errorCode, $errorMessage)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
391
    {
392
        $exception = null;
393
394
        switch ($errorCode) {
395
            case SinchErrorCode::FORBIDDEN_REQUEST:
396
                $exception = new SinchForbiddenRequestException($errorMessage);
397
                break;
398
            case SinchErrorCode::INVALID_AUTHORIZATION_SCHEME_FOR_CALLING_THE_METHOD:
399
                $exception = new SinchInvalidAuthorizationSchemeException($errorMessage);
400
                break;
401
            case SinchErrorCode::NO_VERIFIED_PHONE_NUMBER_ON_YOUR_SINCH_ACCOUNT:
402
            case SinchErrorCode::SANDBOX_SMS_ONLY_ALLOWED_TO_BE_SENT_TO_VERIFIED_NUMBERS:
403
                $exception = new SinchNoVerifiedPhoneNumberException($errorMessage);
404
                break;
405
        }
406
407
        return $exception;
408
    }
409
410
    /**
411
     * Get Sinch exception for internal server error
412
     *
413
     * @param int    $errorCode    Sinch error code
414
     * @param string $errorMessage Sinch error message
415
     *
416
     * @return \Fresh\SinchBundle\Exception\SinchException|null
417
     */
418 View Code Duplication
    private function getSinchExceptionForInternalServerError($errorCode, $errorMessage)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
419
    {
420
        $exception = null;
421
422
        if (SinchErrorCode::INTERNAL_ERROR === $errorCode) {
423
            $exception = new SinchInternalErrorException($errorMessage);
424
        }
425
426
        return $exception;
427
    }
428
429
    // endregion
430
}
431