Completed
Push — master ( 3e9c3d...99411a )
by Hugo
22s queued 11s
created

HttpClient::doRequest()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 9.36
c 0
b 0
f 0
cc 4
nc 1
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yproximite\WannaSpeakBundle;
6
7
use Psr\Log\LoggerInterface;
8
use Psr\Log\NullLogger;
9
use Symfony\Component\HttpClient\ScopingHttpClient;
10
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
11
use Symfony\Contracts\HttpClient\ResponseInterface;
12
use Yproximite\WannaSpeakBundle\Exception\Api\WannaSpeakApiException;
13
use Yproximite\WannaSpeakBundle\Exception\Api\WannaSpeakApiExceptionInterface;
14
use Yproximite\WannaSpeakBundle\Exception\InvalidResponseException;
15
use Yproximite\WannaSpeakBundle\Exception\TestModeException;
16
17
class HttpClient implements HttpClientInterface
18
{
19
    private $accountId;
20
    private $secretKey;
21
    private $test;
22
    private $client;
23
    private $logger;
24
25
    public function __construct(
26
        string $accountId,
27
        string $secretKey,
28
        string $baseUri,
29
        bool $test,
30
        \Symfony\Contracts\HttpClient\HttpClientInterface $client,
31
        ?LoggerInterface $logger = null
32
    ) {
33
        $this->accountId = $accountId;
34
        $this->secretKey = $secretKey;
35
        $this->test      = $test;
36
        $this->client    = ScopingHttpClient::forBaseUri($client, $baseUri);
37
        $this->logger    = $logger ?? new NullLogger();
38
    }
39
40
    public function request(string $api, string $method, array $arguments = []): ResponseInterface
41
    {
42
        $this->logger->info('[wanna-speak] Requesting WannaSpeak API {api} with method {method}.', [
43
            'api'       => $api,
44
            'method'    => $method,
45
            'arguments' => $arguments,
46
        ]);
47
48
        if ($this->test) {
49
            throw new TestModeException();
50
        }
51
52
        $response = $this->doRequest($api, $method, $arguments);
53
54
        $this->handleResponse($response);
55
56
        return $response;
57
    }
58
59
    /**
60
     * @param array<string,mixed> $additionalArguments Additional WannaSpeak request arguments
61
     *
62
     * @throws WannaSpeakApiExceptionInterface
63
     * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
64
     * @throws \Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface
65
     * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
66
     * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
67
     * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
68
     */
69
    private function doRequest(string $api, string $method, array $additionalArguments = []): ResponseInterface
70
    {
71
        $fields = array_merge($additionalArguments, [
72
            'id'     => $this->accountId,
73
            'key'    => $this->getAuthKey(),
74
            'api'    => $api,
75
            'method' => $method,
76
        ]);
77
78
        // Prevent FormDataPart to throw when encountering a non-string value
79
        $fields = array_reduce(array_keys($fields), function (array $acc, string $fieldKey) use ($fields) {
80
            $fieldValue = $fields[$fieldKey];
81
82
            if (true === $fieldValue) {
83
                $fieldValue = '1';
84
            } elseif (false === $fieldValue) {
85
                $fieldValue = '0';
86
            } elseif (is_int($fieldValue)) {
87
                $fieldValue = (string) $fieldValue;
88
            }
89
90
            $acc[$fieldKey] = $fieldValue;
91
92
            return $acc;
93
        }, []);
94
95
        $formData = new FormDataPart($fields);
96
97
        $options = [
98
            'headers' => $formData->getPreparedHeaders()->toArray(),
99
            'body'    => $formData->bodyToIterable(),
100
        ];
101
102
        return $this->client->request('POST', '', $options);
103
    }
104
105
    private function getAuthKey(): string
106
    {
107
        $timeStamp = time();
108
109
        return $timeStamp.'-'.md5($this->accountId.$timeStamp.$this->secretKey);
110
    }
111
112
    /**
113
     * @throws WannaSpeakApiExceptionInterface
114
     * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
115
     * @throws \Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface
116
     * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
117
     * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
118
     * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
119
     */
120
    private function handleResponse(ResponseInterface $response): void
121
    {
122
        $responseData = $response->toArray();
123
124
        if (array_key_exists('error', $responseData)) {
125
            if (null === $responseData['error']) {
126
                return;
127
            }
128
129
            if (is_string($responseData['error'])) {
130
                throw WannaSpeakApiException::create(-1, $responseData['error']);
131
            }
132
133
            if (is_array($responseData['error']) && array_key_exists('nb', $responseData['error'])) {
134
                $statusCode = $responseData['error']['nb'];
135
                // Not possible with JSON format, but just in case of...
136
                if (200 === $statusCode) {
137
                    return;
138
                }
139
140
                throw WannaSpeakApiException::create($statusCode, $responseData['error']['txt'] ?? 'No message.');
141
            }
142
143
            throw new InvalidResponseException(sprintf('Unable to handle field "error" from the response, value is: "%s".', get_debug_type($responseData['error'])));
144
        }
145
    }
146
}
147