Passed
Pull Request — master (#31)
by
unknown
02:09
created

Client::buildQuery()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 7
eloc 13
c 1
b 0
f 1
nc 12
nop 1
dl 0
loc 24
rs 8.8333
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
14
     * https://api.deepl.com/v2/[resource]?auth_key=[yourAuthKey]
15
     */
16
    const API_URL_BASE = '%s://%s/v%s/%s?auth_key=%s';
17
18
    /**
19
     * API BASE URL without authentication query parameter
20
     * https://api.deepl.com/v2/[resource]
21
     */
22
    const API_URL_BASE_NO_AUTH = '%s://%s/v%s/%s';
23
24
    /**
25
     * DeepL API Version (v2 is default since 2018)
26
     *
27
     * @var integer
28
     */
29
    protected $apiVersion;
30
31
    /**
32
     * DeepL API Auth Key (DeepL Pro access required)
33
     *
34
     * @var string
35
     */
36
    protected $authKey;
37
38
    /**
39
     * cURL resource
40
     *
41
     * @var resource
42
     */
43
    protected $curl;
44
45
    /**
46
     * Hostname of the API (in most cases api.deepl.com)
47
     *
48
     * @var string
49
     */
50
    protected $host;
51
52
    /**
53
     * Maximum number of seconds the query should take
54
     *
55
     * @var int|null
56
     */
57
    protected $timeout = null;
58
59
    /**
60
     * URL of the proxy used to connect to DeepL (if needed)
61
     *
62
     * @var string|null
63
     */
64
    protected $proxy = null;
65
66
    /**
67
     * Credentials for the proxy used to connect to DeepL (username:password)
68
     *
69
     * @var string|null
70
     */
71
    protected $proxyCredentials = null;
72
73
    /**
74
     * DeepL constructor
75
     *
76
     * @param string  $authKey
77
     * @param integer $apiVersion
78
     * @param string  $host
79
     */
80
    public function __construct($authKey, $apiVersion = 2, $host = 'api.deepl.com')
81
    {
82
        $this->authKey    = $authKey;
83
        $this->apiVersion = $apiVersion;
84
        $this->host       = $host;
85
        $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...
86
    }
87
88
    /**
89
     * DeepL destructor
90
     */
91
    public function __destruct()
92
    {
93
        if ($this->curl && is_resource($this->curl)) {
94
            curl_close($this->curl);
95
        }
96
    }
97
98
    /**
99
     * Make a request to the given URL
100
     *
101
     * @param string $url
102
     * @param string $body
103
     * @param string $method
104
     *
105
     * @return array
106
     *
107
     * @throws DeepLException
108
     */
109
    public function request($url, $body = '', $method = 'POST')
110
    {
111
        switch ($method) {
112
            case 'DELETE':
113
                curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
114
                break;
115
            case 'POST':
116
                curl_setopt($this->curl, CURLOPT_POST, true);
117
                curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
118
                break;
119
            case 'GET':
120
                curl_setopt($this->curl, CURLOPT_POST, false);
121
                curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'GET');
122
                break;
123
            default:
124
                break;
125
        }
126
127
        curl_setopt($this->curl, CURLOPT_URL, $url);
128
129
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
130
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, array("Authorization: DeepL-Auth-Key $this->authKey"));
131
132
        if ($this->proxy !== null) {
133
            curl_setopt($this->curl, CURLOPT_PROXY, $this->proxy);
134
        }
135
136
        if ($this->proxyCredentials !== null) {
137
            curl_setopt($this->curl, CURLOPT_PROXYAUTH, $this->proxyCredentials);
138
        }
139
140
        if ($this->timeout !== null) {
141
            curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->timeout);
142
        }
143
144
        $response = curl_exec($this->curl);
145
146
        if (curl_errno($this->curl)) {
147
            throw new DeepLException('There was a cURL Request Error : ' . curl_error($this->curl));
148
        }
149
        $httpCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
150
151
        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...
152
    }
153
154
    /**
155
     * Set a proxy to use for querying the DeepL API if needed
156
     *
157
     * @param string $proxy Proxy URL (e.g 'http://proxy-domain.com:3128')
158
     */
159
    public function setProxy($proxy)
160
    {
161
162
        $this->proxy = $proxy;
163
    }
164
165
    /**
166
     * Set the proxy credentials
167
     *
168
     * @param string $proxyCredentials proxy credentials (using 'username:password' format)
169
     */
170
    public function setProxyCredentials($proxyCredentials)
171
    {
172
        $this->proxyCredentials = $proxyCredentials;
173
    }
174
175
    /**
176
     * Set a timeout for queries to the DeepL API
177
     *
178
     * @param int $timeout Timeout in seconds
179
     */
180
    public function setTimeout($timeout)
181
    {
182
        $this->timeout = $timeout;
183
    }
184
185
    /**
186
     * Creates the Base-Url which all the API-resources have in common.
187
     *
188
     * @param string $resource
189
     * @param bool   $withAuth
190
     *
191
     * @return string
192
     */
193
    public function buildBaseUrl(string $resource = 'translate'): string
194
    {
195
        if (!empty($this->authKey)) {
196
            return sprintf(
197
                self::API_URL_BASE,
198
                self::API_URL_SCHEMA,
199
                $this->host,
200
                $this->apiVersion,
201
                $resource,
202
                $this->authKey
203
            );
204
        }
205
206
        return sprintf(
207
            self::API_URL_BASE_NO_AUTH,
208
            self::API_URL_SCHEMA,
209
            $this->host,
210
            $this->apiVersion,
211
            $resource
212
        );
213
    }
214
215
    /**
216
     * @param array $paramsArray
217
     *
218
     * @return string
219
     */
220
    public function buildQuery($paramsArray)
221
    {
222
        if (isset($paramsArray['text']) && true === is_array($paramsArray['text'])) {
223
            $text = $paramsArray['text'];
224
            unset($paramsArray['text']);
225
            $textString = '';
226
            foreach ($text as $textElement) {
227
                $textString .= '&text='.rawurlencode($textElement);
228
            }
229
        }
230
231
        foreach ($paramsArray as $key => $value) {
232
            if (true === is_array($value)) {
233
                $paramsArray[$key] = implode(',', $value);
234
            }
235
        }
236
237
        $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

237
        $body = http_build_query($paramsArray, /** @scrutinizer ignore-type */ null, '&');
Loading history...
238
239
        if (isset($textString)) {
240
            $body = $textString.'&'.$body;
241
        }
242
243
        return $body;
244
    }
245
246
    /**
247
     * Handles the different kind of response returned from API, array, string or null
248
     *
249
     * @param $response
250
     * @param $httpCode
251
     * @return array|mixed|null
252
     * @throws DeepLException
253
     */
254
    private function handleResponse($response, $httpCode)
255
    {
256
        $responseArray = json_decode($response, true);
257
        if (($httpCode === 200 || $httpCode === 204) && is_null($responseArray)) {
258
            return empty($response) ? null : $response;
259
        }
260
261
        if ($httpCode !== 200 && is_array($responseArray) && array_key_exists('message', $responseArray)) {
262
            throw new DeepLException($responseArray['message'], $httpCode);
263
        }
264
265
        if (!is_array($responseArray)) {
266
            throw new DeepLException('The Response seems to not be valid JSON.', $httpCode);
267
        }
268
269
        return $responseArray;
270
    }
271
}
272