Passed
Push — master ( b68d5d...4c5c58 )
by
unknown
02:25 queued 13s
created

DeepL::removeEmptyParams()   A

Complexity

Conditions 6
Paths 11

Size

Total Lines 22
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 9
c 2
b 0
f 1
dl 0
loc 22
ccs 15
cts 15
cp 1
rs 9.2222
cc 6
nc 11
nop 1
crap 6
1
<?php
2
3
namespace BabyMarkt\DeepL;
4
5
/**
6
 * DeepL API client library
7
 *
8
 * @package BabyMarkt\DeepL
9
 */
10
class DeepL
11
{
12
    const API_URL_SCHEMA = 'https';
13
    /**
14
     * API BASE URL
15
     * https://api.deepl.com/v2/[resource]?auth_key=[yourAuthKey]
16
     */
17
    const API_URL_BASE = '%s://%s/v%s/%s?auth_key=%s';
18
19
    /**
20
     * API URL: usage
21
     */
22
    const API_URL_RESOURCE_USAGE = 'usage';
23
24
    /**
25
     * API URL: languages
26
     */
27
    const API_URL_RESOURCE_LANGUAGES = 'languages';
28
29
    /**
30
     * DeepL API Version (v2 is default since 2018)
31
     *
32
     * @var integer
33
     */
34
    protected $apiVersion;
35
36
    /**
37
     * DeepL API Auth Key (DeepL Pro access required)
38
     *
39
     * @var string
40
     */
41
    protected $authKey;
42
43
    /**
44
     * cURL resource
45
     *
46
     * @var resource
47
     */
48
    protected $curl;
49
50
    /**
51
     * Hostname of the API (in most cases api.deepl.com)
52
     *
53
     * @var string
54
     */
55
    protected $host;
56
57
    /**
58
     * URL of the proxy used to connect to DeepL (if needed)
59
     *
60
     * @var string|null
61
     */
62
    protected $proxy = null;
63
64
    /**
65
     * Credentials for the proxy used to connect to DeepL (username:password)
66
     *
67
     * @var string|null
68
     */
69
    protected $proxyCredentials = null;
70
71
    /**
72
     * Maximum number of seconds the query should take
73
     *
74
     * @var int|null
75
     */
76
    protected $timeout = null;
77
78
    /**
79
     * DeepL constructor
80
     *
81
     * @param string  $authKey
82
     * @param integer $apiVersion
83
     * @param string  $host
84
     */
85 31
    public function __construct($authKey, $apiVersion = 2, $host = 'api.deepl.com')
86
    {
87 31
        $this->authKey    = $authKey;
88 31
        $this->apiVersion = $apiVersion;
89 31
        $this->host       = $host;
90 31
        $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...
91
92 31
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1);
93 31
    }
94
95
    /**
96
     * DeepL destructor
97
     */
98 31
    public function __destruct()
99
    {
100 31
        if ($this->curl && is_resource($this->curl)) {
101 31
            curl_close($this->curl);
102 31
        }
103 31
    }
104
105
    /**
106
     * Call languages-Endpoint and return Json-response as an Array
107
     *
108
     * @param string $type
109
     *
110
     * @return array
111
     * @throws DeepLException
112
     */
113 4
    public function languages($type = null)
114
    {
115 4
        $url       = $this->buildBaseUrl(self::API_URL_RESOURCE_LANGUAGES);
116 4
        $body      = $this->buildQuery(array('type' => $type));
117 4
        $languages = $this->request($url, $body);
118
119 3
        return $languages;
120
    }
121
122
    /**
123
     * Set a proxy to use for querying the DeepL API if needed
124
     *
125
     * @param string $proxy Proxy URL (e.g 'http://proxy-domain.com:3128')
126
     */
127
    public function setProxy($proxy)
128
    {
129
130
        $this->proxy = $proxy;
131
    }
132
133
    /**
134
     * Set the proxy credentials
135
     *
136
     * @param string $proxyCredentials proxy credentials (using 'username:password' format)
137
     */
138
    public function setProxyCredentials($proxyCredentials)
139
    {
140
        $this->proxyCredentials = $proxyCredentials;
141
    }
142
143
    /**
144
     * Set a timeout for queries to the DeepL API
145
     *
146
     * @param int $timeout Timeout in seconds
147
     */
148 1
    public function setTimeout($timeout)
149
    {
150 1
        $this->timeout = $timeout;
151 1
    }
152
153
    /**
154
     * Translate the text string or array from source to destination language
155
     * For detailed info on Parameters see README.md
156
     *
157
     * @param string|string[] $text
158
     * @param string          $sourceLang
159
     * @param string          $targetLang
160
     * @param string          $tagHandling
161
     * @param array|null      $ignoreTags
162
     * @param string          $formality
163
     * @param null            $splitSentences
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $splitSentences is correct as it would always require null to be passed?
Loading history...
164
     * @param null            $preserveFormatting
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $preserveFormatting is correct as it would always require null to be passed?
Loading history...
165
     * @param array|null      $nonSplittingTags
166
     * @param null            $outlineDetection
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $outlineDetection is correct as it would always require null to be passed?
Loading history...
167
     * @param array|null      $splittingTags
168
     *
169
     * @return array
170
     * @throws DeepLException
171
     *
172
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
173
     */
174 13
    public function translate(
175
        $text,
176
        $sourceLang = 'de',
177
        $targetLang = 'en',
178
        $tagHandling = null,
179
        array $ignoreTags = null,
180
        $formality = 'default',
181
        $splitSentences = null,
182
        $preserveFormatting = null,
183
        array $nonSplittingTags = null,
184
        $outlineDetection = null,
185
        array $splittingTags = null
186
    ) {
187 13
        if (is_array($tagHandling)) {
0 ignored issues
show
introduced by
The condition is_array($tagHandling) is always false.
Loading history...
188 1
            throw new \InvalidArgumentException('$tagHandling must be of type String in V2 of DeepLLibrary');
189
        }
190
        $paramsArray = array(
191 12
            'text'                => $text,
192 12
            'source_lang'         => $sourceLang,
193 12
            'target_lang'         => $targetLang,
194 12
            'splitting_tags'      => $splittingTags,
195 12
            'non_splitting_tags'  => $nonSplittingTags,
196 12
            'ignore_tags'         => $ignoreTags,
197 12
            'tag_handling'        => $tagHandling,
198 12
            'formality'           => $formality,
199 12
            'split_sentences'     => $splitSentences,
200 12
            'preserve_formatting' => $preserveFormatting,
201 12
            'outline_detection'   => $outlineDetection,
202 12
        );
203
204 12
        $paramsArray = $this->removeEmptyParams($paramsArray);
205 12
        $url         = $this->buildBaseUrl();
206 12
        $body        = $this->buildQuery($paramsArray);
207
208
        // request the DeepL API
209 12
        $translationsArray = $this->request($url, $body);
210
211 7
        return $translationsArray['translations'];
212
    }
213
214
    /**
215
     * Calls the usage-Endpoint and return Json-response as an array
216
     *
217
     * @return array
218
     * @throws DeepLException
219
     */
220 1
    public function usage()
221
    {
222 1
        $url   = $this->buildBaseUrl(self::API_URL_RESOURCE_USAGE);
223 1
        $usage = $this->request($url);
224
225 1
        return $usage;
226
    }
227
228
229
    /**
230
     * Creates the Base-Url which all of the 3 API-resources have in common.
231
     *
232
     * @param string $resource
233
     *
234
     * @return string
235
     */
236 19
    protected function buildBaseUrl($resource = 'translate')
237
    {
238 19
        $url = sprintf(
239 19
            self::API_URL_BASE,
240 19
            self::API_URL_SCHEMA,
241 19
            $this->host,
242 19
            $this->apiVersion,
243 19
            $resource,
244 19
            $this->authKey
245 19
        );
246
247 19
        return $url;
248
    }
249
250
    /**
251
     * @param array $paramsArray
252
     *
253
     * @return string
254
     */
255 22
    protected function buildQuery($paramsArray)
256
    {
257 22
        if (isset($paramsArray['text']) && true === is_array($paramsArray['text'])) {
258 2
            $text = $paramsArray['text'];
259 2
            unset($paramsArray['text']);
260 2
            $textString = '';
261 2
            foreach ($text as $textElement) {
262 2
                $textString .= '&text='.rawurlencode($textElement);
263 2
            }
264 2
        }
265
266 22
        foreach ($paramsArray as $key => $value) {
267 22
            if (true === is_array($value)) {
268 7
                $paramsArray[$key] = implode(',', $value);
269 7
            }
270 22
        }
271
272 22
        $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

272
        $body = http_build_query($paramsArray, /** @scrutinizer ignore-type */ null, '&');
Loading history...
273
274 22
        if (isset($textString)) {
275 2
            $body = $textString.'&'.$body;
276 2
        }
277
278 22
        return $body;
279
    }
280
281
282
283
284
    /**
285
     * Make a request to the given URL
286
     *
287
     * @param string $url
288
     * @param string $body
289
     *
290
     * @return array
291
     *
292
     * @throws DeepLException
293
     */
294 17
    protected function request($url, $body = '')
295
    {
296 17
        curl_setopt($this->curl, CURLOPT_POST, true);
297 17
        curl_setopt($this->curl, CURLOPT_URL, $url);
298 17
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
299 17
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
300
301 17
        if ($this->proxy !== null) {
302
            curl_setopt($this->curl, CURLOPT_PROXY, $this->proxy);
303
        }
304
305 17
        if ($this->proxyCredentials !== null) {
306
            curl_setopt($this->curl, CURLOPT_PROXYAUTH, $this->proxyCredentials);
307
        }
308
309 17
        if ($this->timeout !== null) {
310 1
            curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->timeout);
311 1
        }
312
313 17
        $response = curl_exec($this->curl);
314
315 17
        if (curl_errno($this->curl)) {
316 1
            throw new DeepLException('There was a cURL Request Error : ' . curl_error($this->curl));
317
        }
318 16
        $httpCode      = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
319 16
        $responseArray = json_decode($response, true);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

319
        $responseArray = json_decode(/** @scrutinizer ignore-type */ $response, true);
Loading history...
320
321 16
        if ($httpCode != 200 && is_array($responseArray) && array_key_exists('message', $responseArray)) {
322 3
            throw new DeepLException($responseArray['message'], $httpCode);
323
        }
324
325 13
        if (false === is_array($responseArray)) {
326 2
            throw new DeepLException('The Response seems to not be valid JSON.', $httpCode);
327
        }
328
329 11
        return $responseArray;
330
    }
331
332
    /**
333
     * @param array $paramsArray
334
     *
335
     * @return array
336
     */
337 17
    private function removeEmptyParams($paramsArray)
338
    {
339
340 17
        foreach ($paramsArray as $key => $value) {
341 17
            if (true === empty($value)) {
342 17
                unset($paramsArray[$key]);
343 17
            }
344
            // Special Workaround for outline_detection which will be unset above
345
            // DeepL assumes outline_detection=1 if it is not send
346
            // in order to deactivate it, we need to send outline_detection=0 to the api
347 17
            if ('outline_detection' === $key) {
348 17
                if (1 === $value) {
349 2
                    unset($paramsArray[$key]);
350 2
                }
351
352 17
                if (0 === $value) {
353 3
                    $paramsArray[$key] = 0;
354 3
                }
355 17
            }
356 17
        }
357
358 17
        return $paramsArray;
359
    }
360
}
361