Passed
Pull Request — master (#35)
by
unknown
08:29
created

Client::handleResponse()   D

Complexity

Conditions 18
Paths 13

Size

Total Lines 57
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 18
eloc 36
c 2
b 0
f 1
nc 13
nop 2
dl 0
loc 57
rs 4.8666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace BabyMarkt\DeepL;
4
5
/**
6
 * Class Client implements a DeepL http Client based on PHP-CURL
7
 */
8
final class Client implements ClientInterface
9
{
10
    const API_URL_SCHEMA = 'https';
11
12
    /**
13
     * API BASE URL without authentication query parameter
14
     * https://api.deepl.com/v2/[resource]
15
     */
16
    const API_URL_BASE_NO_AUTH = '%s://%s/v%s/%s';
17
18
    /**
19
     * DeepL API Version (v2 is default since 2018)
20
     *
21
     * @var integer
22
     */
23
    protected $apiVersion;
24
25
    /**
26
     * DeepL API Auth Key (DeepL Pro access required)
27
     *
28
     * @var string
29
     */
30
    protected $authKey;
31
32
    /**
33
     * cURL resource
34
     *
35
     * @var resource
36
     */
37
    protected $curl;
38
39
    /**
40
     * Hostname of the API (in most cases api.deepl.com)
41
     *
42
     * @var string
43
     */
44
    protected $host;
45
46
    /**
47
     * Maximum number of seconds the query should take
48
     *
49
     * @var int|null
50
     */
51
    protected $timeout = null;
52
53
    /**
54
     * URL of the proxy used to connect to DeepL (if needed)
55
     *
56
     * @var string|null
57
     */
58
    protected $proxy = null;
59
60
    /**
61
     * Credentials for the proxy used to connect to DeepL (username:password)
62
     *
63
     * @var string|null
64
     */
65
    protected $proxyCredentials = null;
66
67
    /**
68
     * DeepL constructor
69
     *
70
     * @param string  $authKey
71
     * @param integer $apiVersion
72
     * @param string  $host
73
     */
74
    public function __construct($authKey, $apiVersion = 2, $host = 'api.deepl.com')
75
    {
76
        $this->authKey    = $authKey;
77
        $this->apiVersion = $apiVersion;
78
        $this->host       = $host;
79
        $this->curl       = curl_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like curl_init() can also be of type CurlHandle. However, the property $curl is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
80
81
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1);
82
    }
83
84
    /**
85
     * DeepL destructor
86
     */
87
    public function __destruct()
88
    {
89
        if ($this->curl && is_resource($this->curl)) {
90
            curl_close($this->curl);
91
        }
92
    }
93
94
    /**
95
     * Make a request to the given URL
96
     *
97
     * @param string $url
98
     * @param string $body
99
     * @param string $method
100
     *
101
     * @return array
102
     *
103
     * @throws DeepLException
104
     */
105
    public function request($url, $body = '', $method = 'POST')
106
    {
107
        switch ($method) {
108
            case 'DELETE':
109
                curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
110
                break;
111
            case 'POST':
112
                curl_setopt($this->curl, CURLOPT_POST, true);
113
                curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
114
                break;
115
            case 'GET':
116
            default:
117
                break;
118
        }
119
120
        curl_setopt($this->curl, CURLOPT_URL, $url);
121
122
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
123
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, ['Authorization: DeepL-Auth-Key ' . $this->authKey]);
124
125
        if ($this->proxy !== null) {
126
            curl_setopt($this->curl, CURLOPT_PROXY, $this->proxy);
127
        }
128
129
        if ($this->proxyCredentials !== null) {
130
            curl_setopt($this->curl, CURLOPT_PROXYAUTH, $this->proxyCredentials);
131
        }
132
133
        if ($this->timeout !== null) {
134
            curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->timeout);
135
        }
136
137
        $response = curl_exec($this->curl);
138
139
        if (curl_errno($this->curl)) {
140
            throw new DeepLException('There was a cURL Request Error : ' . curl_error($this->curl));
141
        }
142
        $httpCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
143
144
        return $this->handleResponse($response, $httpCode);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->handleResp...e($response, $httpCode) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
145
    }
146
147
    /**
148
     * Set a proxy to use for querying the DeepL API if needed
149
     *
150
     * @param string $proxy Proxy URL (e.g 'http://proxy-domain.com:3128')
151
     */
152
    public function setProxy($proxy)
153
    {
154
        $this->proxy = $proxy;
155
    }
156
157
    /**
158
     * Set the proxy credentials
159
     *
160
     * @param string $proxyCredentials proxy credentials (using 'username:password' format)
161
     */
162
    public function setProxyCredentials($proxyCredentials)
163
    {
164
        $this->proxyCredentials = $proxyCredentials;
165
    }
166
167
    /**
168
     * Set a timeout for queries to the DeepL API
169
     *
170
     * @param int $timeout Timeout in seconds
171
     */
172
    public function setTimeout($timeout)
173
    {
174
        $this->timeout = $timeout;
175
    }
176
177
    /**
178
     * Creates the Base-Url which all the API-resources have in common.
179
     *
180
     * @param string $resource
181
     * @param bool   $withAuth
182
     *
183
     * @return string
184
     */
185
    public function buildBaseUrl(string $resource = 'translate'): string
186
    {
187
        return sprintf(
188
            self::API_URL_BASE_NO_AUTH,
189
            self::API_URL_SCHEMA,
190
            $this->host,
191
            $this->apiVersion,
192
            $resource
193
        );
194
    }
195
196
    /**
197
     * @param array $paramsArray
198
     *
199
     * @return string
200
     */
201
    public function buildQuery($paramsArray)
202
    {
203
        if (isset($paramsArray['text']) && true === is_array($paramsArray['text'])) {
204
            $text = $paramsArray['text'];
205
            unset($paramsArray['text']);
206
            $textString = '';
207
            foreach ($text as $textElement) {
208
                $textString .= '&text=' . rawurlencode($textElement);
209
            }
210
        }
211
212
        foreach ($paramsArray as $key => $value) {
213
            if (true === is_array($value)) {
214
                $paramsArray[$key] = implode(',', $value);
215
            }
216
        }
217
218
        $body = http_build_query($paramsArray, null, '&');
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $numeric_prefix of http_build_query(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

218
        $body = http_build_query($paramsArray, /** @scrutinizer ignore-type */ null, '&');
Loading history...
219
220
        if (isset($textString)) {
221
            $body = $textString . '&' . $body;
222
        }
223
224
        return $body;
225
    }
226
227
    /**
228
     * Handles the different kind of response returned from API, array, string or null
229
     *
230
     * @param $response
231
     * @param $httpCode
232
     *
233
     * @return array|mixed|null
234
     *
235
     * @throws DeepLException
236
     */
237
    private function handleResponse($response, $httpCode)
238
    {
239
        $responseArray = json_decode($response, true);
240
        if (($httpCode === 200 || $httpCode === 204) && is_null($responseArray)) {
241
            return empty($response) ? null : $response;
242
        }
243
244
        if ($httpCode !== 200 && is_array($responseArray) && array_key_exists('message', $responseArray)) {
245
            throw new DeepLException($responseArray['message'], $httpCode);
246
        }
247
248
		/**
249
		 * @see https://www.deepl.com/docs-api/accessing-the-api/error-handling/
250
		 */
251
		switch ($httpCode) {
252
			case '400':
253
				throw new DeepLException('Bad request. Please check error message and your parameters.', $httpCode);
254
				break;
255
256
			case '403':
257
				throw new DeepLException('Authorization failed. Please supply a valid auth_key parameter.', $httpCode);
258
				break;
259
260
			case '404':
261
				throw new DeepLException('The requested resource could not be found.', $httpCode);
262
				break;
263
264
			case '413':
265
				throw new DeepLException('The request size exceeds the limit.', $httpCode);
266
				break;
267
268
			case '414':
269
				throw new DeepLException('The request URL is too long. You can avoid this error by using a POST request instead of a GET request, and sending the parameters in the HTTP body.', $httpCode);
270
				break;
271
272
			case '429':
273
				throw new DeepLException('Too many requests. Please wait and resend your request.', $httpCode);
274
				break;
275
276
			case '456':
277
				throw new DeepLException('Quota exceeded. The character limit has been reached.', $httpCode);
278
				break;
279
280
			case '503':
281
				throw new DeepLException('Resource currently unavailable. Try again later.', $httpCode);
282
				break;
283
284
			case '529':
285
				throw new DeepLException('Too many requests. Please wait and resend your request.', $httpCode);
286
				break;
287
		}
288
289
        if (!is_array($responseArray)) {
290
            throw new DeepLException('The Response seems to not be valid JSON.', $httpCode);
291
        }
292
293
        return $responseArray;
294
    }
295
}
296