Passed
Pull Request — master (#16)
by
unknown
01:54
created

DeepL::buildUrl()   F

Complexity

Conditions 11
Paths 1024

Size

Total Lines 56
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 14.3659

Importance

Changes 8
Bugs 1 Features 4
Metric Value
eloc 22
c 8
b 1
f 4
dl 0
loc 56
ccs 23
cts 33
cp 0.6969
rs 3.15
cc 11
nc 1024
nop 11
crap 14.3659

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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: translate
21
     */
22
    const API_URL_RESOURCE_TRANSLATE = 'translate';
23
24
    /**
25
     * API URL: usage
26
     */
27
    const API_URL_RESOURCE_USAGE = 'usage';
28
29
    /**
30
     * API URL: languages
31
     */
32
    const API_URL_RESOURCE_LANGUAGES = 'languages';
33
34
35
    /**
36
     * API URL: Parameter text
37
     */
38
    const API_URL_TEXT = 'text=%s';
39
40
    /**
41
     * API URL: Parameter source_lang
42
     */
43
    const API_URL_SOURCE_LANG = 'source_lang=%s';
44
45
    /**
46
     * API URL: Parameter target_lang
47
     */
48
    const API_URL_DESTINATION_LANG = 'target_lang=%s';
49
50
    /**
51
     * API URL: Parameter tag_handling
52
     */
53
    const API_URL_TAG_HANDLING = 'tag_handling=%s';
54
55
    /**
56
     * API URL: Parameter ignore_tags
57
     */
58
    const API_URL_IGNORE_TAGS = 'ignore_tags=%s';
59
60
    /**
61
     * API URL: Parameter formality
62
     */
63
    const API_URL_FORMALITY = 'formality=%s';
64
65
    /**
66
     * API URL: Parameter split_sentences
67
     */
68
    const API_URL_SPLIT_SENTENCES = 'split_sentences=%s';
69
70
    /**
71
     * API URL: Parameter preserve_formatting
72
     */
73
    const API_URL_PRESERVE_FORMATTING = 'preserve_formatting=%s';
74
75
    /**
76
     * API URL: Parameter non_splitting_tags
77
     */
78
    const API_URL_NON_SPLITTING_TAGS = 'non_splitting_tags=%s';
79
80
    /**
81
     * API URL: Parameter outline_detection
82
     */
83
    const API_URL_OUTLINE_DETECTION = 'outline_detection=%s';
84
85
    /**
86
     * API URL: Parameter splitting_tags
87
     */
88
    const API_URL_SPLITTING_TAGS = 'splitting_tags=%s';
89
90
    /**
91
     * DeepL HTTP error codes
92
     *
93
     * @var array
94
     */
95
    protected $errorCodes = array(
96
        400 => 'Wrong request, please check error message and your parameters.',
97
        403 => 'Authorization failed. Please supply a valid auth_key parameter.',
98
        413 => 'Request Entity Too Large. The request size exceeds the current limit.',
99
        429 => 'Too many requests. Please wait and send your request once again.',
100
        456 => 'Quota exceeded. The character limit has been reached.',
101
    );
102
103
    /**
104
     * Supported translation source languages
105
     *
106
     * @var array
107
     */
108
    protected $sourceLanguages = array(
109
        'EN',
110
        'DE',
111
        'FR',
112
        'ES',
113
        'PT',
114
        'IT',
115
        'NL',
116
        'PL',
117
        'RU',
118
        'JA',
119
        'ZH',
120
    );
121
122
    /**
123
     * Supported translation destination languages
124
     *
125
     * @var array
126
     */
127
    protected $destinationLanguages = array(
128
        'EN',
129
        'DE',
130
        'FR',
131
        'ES',
132
        'PT',
133
        'PT-PT',
134
        'PT-BR',
135
        'IT',
136
        'NL',
137
        'PL',
138
        'RU',
139
        'JA',
140
        'ZH',
141
    );
142
143
    /**
144
     * DeepL API Version (v2 is default since 2018)
145
     *
146
     * @var integer
147
     */
148
    protected $apiVersion;
149
150
    /**
151
     * DeepL API Auth Key (DeepL Pro access required)
152
     *
153
     * @var string
154
     */
155
    protected $authKey;
156
157
    /**
158
     * cURL resource
159
     *
160
     * @var resource
161
     */
162
    protected $curl;
163
164
    /**
165
     * Hostname of the API (in most cases api.deepl.com)
166
     *
167
     * @var string
168
     */
169
    protected $host;
170
171
    /**
172
     * DeepL constructor
173
     *
174
     * @param string  $authKey
175
     * @param integer $apiVersion
176
     * @param string  $host
177
     */
178 18
    public function __construct($authKey, $apiVersion = 2, $host = 'api.deepl.com')
179
    {
180 18
        $this->authKey    = $authKey;
181 18
        $this->apiVersion = $apiVersion;
182 18
        $this->host       = $host;
183 18
        $this->curl       = curl_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like curl_init() can also be of type false. 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...
184
185 18
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1);
0 ignored issues
show
Bug introduced by
It seems like $this->curl can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, 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

185
        curl_setopt(/** @scrutinizer ignore-type */ $this->curl, CURLOPT_RETURNTRANSFER, 1);
Loading history...
186 18
    }
187
188
    /**
189
     * DeepL destructor
190
     */
191 18
    public function __destruct()
192
    {
193 18
        if ($this->curl && is_resource($this->curl)) {
194 18
            curl_close($this->curl);
195 18
        }
196 18
    }
197
198
    /**
199
     * Translate the text string or array from source to destination language
200
     *
201
     * @param string|string[] $text
202
     * @param string          $sourceLanguage
203
     * @param string          $destinationLanguage
204
     * @param string          $tagHandling
205
     * @param array|null      $ignoreTags
206
     * @param string          $formality
207
     * @param string          $resource
208
     * @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...
209
     * @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...
210
     * @param array|null      $nonSplittingTags
211
     * @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...
212
     * @param array|null      $splittingTags
213
     *
214
     * @return mixed
215
     * @throws DeepLException
216
     *
217
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
218
     */
219 6
    public function translate(
220
        $text,
221
        $sourceLanguage = 'de',
222
        $destinationLanguage = 'en',
223
        $tagHandling = null,
224
        array $ignoreTags = null,
225
        $formality = "default",
226
        $resource = self::API_URL_RESOURCE_TRANSLATE,
227
        $splitSentences = null,
228
        $preserveFormatting = null,
229
        array $nonSplittingTags = null,
230
        $outlineDetection = null,
231
        array $splittingTags = null
232
    ) {
233
        // make sure we only accept supported languages
234 6
        $this->checkLanguages($sourceLanguage, $destinationLanguage);
235
236
        // build the DeepL API request url
237 6
        $url = $this->buildUrl(
238 6
            $sourceLanguage,
239 6
            $destinationLanguage,
240 6
            $tagHandling,
0 ignored issues
show
Bug introduced by
It seems like $tagHandling can also be of type string; however, parameter $tagHandling of BabyMarkt\DeepL\DeepL::buildUrl() does only seem to accept array, 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

240
            /** @scrutinizer ignore-type */ $tagHandling,
Loading history...
241 6
            $ignoreTags,
242 6
            $formality,
243 6
            $resource,
244 6
            $splitSentences,
245 6
            $preserveFormatting,
246 6
            $nonSplittingTags,
247 6
            $outlineDetection,
248
            $splittingTags
249 6
        );
250
251 6
        $body = $this->buildBody($text);
252
253
        // request the DeepL API
254 6
        $translationsArray = $this->request($url, $body);
255 4
        $translationsCount = count($translationsArray['translations']);
256
257 4
        if ($translationsCount === 0) {
258
            throw new DeepLException('No translations found.');
259 4
        } elseif ($translationsCount === 1) {
260 4
            return $translationsArray['translations'][0]['text'];
261
        }
262
263
        return $translationsArray['translations'];
264
    }
265
266
    /**
267
     * Check if the given languages are supported
268
     *
269
     * @param string $sourceLanguage
270
     * @param string $destinationLanguage
271
     *
272
     * @return boolean
273
     *
274
     * @throws DeepLException
275
     */
276 9
    protected function checkLanguages($sourceLanguage, $destinationLanguage)
277
    {
278 9
        $sourceLanguage = strtoupper($sourceLanguage);
279
280 9
        if (false === in_array($sourceLanguage, $this->sourceLanguages)) {
281 1
            throw new DeepLException(
282 1
                sprintf('The language "%s" is not supported as source language.', $sourceLanguage)
283 1
            );
284
        }
285
286 8
        $destinationLanguage = strtoupper($destinationLanguage);
287
288 8
        if (false === in_array($destinationLanguage, $this->destinationLanguages)) {
289 1
            throw new DeepLException(
290 1
                sprintf('The language "%s" is not supported as destination language.', $destinationLanguage)
291 1
            );
292
        }
293
294 7
        return true;
295
    }
296
297
    /**
298
     * Build the URL for the DeepL API request
299
     *
300
     * @param string $sourceLanguage
301
     * @param string $destinationLanguage
302
     * @param array  $tagHandling
303
     * @param array  $ignoreTags
304
     * @param string $formality
305
     * @param string $resource
306
     *
307
     * @return string
308
     *
309
     * @SuppressWarnings(PHPMD.NPathComplexity)
310
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
311
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
312
     */
313 10
    protected function buildUrl(
314
        $sourceLanguage = null,
315
        $destinationLanguage = null,
316
        $tagHandling = null,
317
        array $ignoreTags = null,
318
        $formality = 'default',
319
        $resource = 'translate',
320
        $splitSentences = null,
321
        $preserveFormatting = null,
322
        array $nonSplittingTags = null,
323
        $outlineDetection = null,
324
        array $splittingTags = null
325
    ) {
326 10
        $url = $this->buildBaseUrl($resource);
327
328 10
        if (false === empty($sourceLanguage)) {
329 10
            $url .= '&'.sprintf(self::API_URL_SOURCE_LANG, strtolower($sourceLanguage));
330 10
        }
331
332 10
        if (false === empty($destinationLanguage)) {
333 10
            $url .= '&'.sprintf(self::API_URL_DESTINATION_LANG, strtolower($destinationLanguage));
334 10
        }
335
336 10
        if (false === empty($tagHandling)) {
337 4
            $url .= '&'.sprintf(self::API_URL_TAG_HANDLING, $tagHandling);
0 ignored issues
show
Bug introduced by
$tagHandling of type array is incompatible with the type string expected by parameter $args of sprintf(). ( Ignorable by Annotation )

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

337
            $url .= '&'.sprintf(self::API_URL_TAG_HANDLING, /** @scrutinizer ignore-type */ $tagHandling);
Loading history...
338 4
        }
339
340 10
        if (false === empty($ignoreTags)) {
341 3
            $url .= '&'.sprintf(self::API_URL_IGNORE_TAGS, implode(',', $ignoreTags));
342 3
        }
343
344 10
        if (false === empty($formality)) {
345 10
            $url .= '&'.sprintf(self::API_URL_FORMALITY, $formality);
346 10
        }
347
348 10
        if (false === empty($splitSentences)) {
349
            $url .= '&'.sprintf(self::API_URL_SPLIT_SENTENCES, $splitSentences);
350
        }
351
352 10
        if (false === empty($preserveFormatting)) {
353
            $url .= '&'.sprintf(self::API_URL_PRESERVE_FORMATTING, $preserveFormatting);
354
        }
355
356 10
        if (false === empty($nonSplittingTags)) {
357
            $url .= '&'.sprintf(self::API_URL_NON_SPLITTING_TAGS, implode(',', $nonSplittingTags));
358
        }
359
360 10
        if (false === empty($outlineDetection)) {
361
            $url .= '&'.sprintf(self::API_URL_OUTLINE_DETECTION, $outlineDetection);
362
        }
363
364 10
        if (false === empty($splittingTags)) {
365
            $url .= '&'.sprintf(self::API_URL_SPLITTING_TAGS, implode(',', $splittingTags));
366
        }
367
368 10
        return $url;
369
    }
370
371
    /**
372
     * Creates the Base-Url which all of the 3 API-recourses have in common.
373
     *
374
     * @param string $resource
375
     *
376
     * @return string
377
     */
378 14
    protected function buildBaseUrl($resource = 'translate')
379
    {
380 14
        $url = sprintf(
381 14
            self::API_URL_BASE,
382 14
            self::API_URL_SCHEMA,
383 14
            $this->host,
384 14
            $this->apiVersion,
385 14
            $resource,
386 14
            $this->authKey
387 14
        );
388
389 14
        return $url;
390
    }
391
392
    /**
393
     * Build the body for the DeepL API request
394
     *
395
     * @param string|string[] $text
396
     *
397
     * @return string
398
     */
399 7
    protected function buildBody($text)
400
    {
401 7
        $body  = '';
402 7
        $first = true;
403
404 7
        if (!is_array($text)) {
405 7
            $text = (array)$text;
406 7
        }
407
408 7
        foreach ($text as $textElement) {
409 7
            $body .= ($first ? '' : '&').sprintf(self::API_URL_TEXT, rawurlencode($textElement));
410
411 7
            if ($first) {
412 7
                $first = false;
413 7
            }
414 7
        }
415
416 7
        return $body;
417
    }
418
419
    /**
420
     * Make a request to the given URL
421
     *
422
     * @param string $url
423
     *
424
     * @return array
425
     *
426
     * @throws DeepLException
427
     */
428 8
    protected function request($url, $body)
429
    {
430 8
        curl_setopt($this->curl, CURLOPT_POST, true);
431 8
        curl_setopt($this->curl, CURLOPT_URL, $url);
432 8
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
433 8
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
434
435 8
        $response = curl_exec($this->curl);
436
437 8
        if (curl_errno($this->curl)) {
438
            throw new DeepLException('There was a cURL Request Error.');
439
        }
440
441 8
        $httpCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
442
443 8
        if ($httpCode != 200 && array_key_exists($httpCode, $this->errorCodes)) {
444 2
            throw new DeepLException($this->errorCodes[$httpCode], $httpCode);
445
        }
446
447 6
        $translationsArray = json_decode($response, true);
448
449 6
        if (!$translationsArray) {
450
            throw new DeepLException('The Response seems to not be valid JSON.');
451
        }
452
453 6
        return $translationsArray;
454
    }
455
456
    /**
457
     * Calls the usage-Endpoint and return Json-response as an array
458
     *
459
     * @return array
460
     * @throws DeepLException
461
     */
462 1
    public function usage()
463
    {
464 1
        $body  = '';
465 1
        $url   = $this->buildBaseUrl(self::API_URL_RESOURCE_USAGE);
466 1
        $usage = $this->request($url, $body);
467
468 1
        return $usage;
469
    }
470
471
    /**
472
     * Call languages-Endpoint and return Json-response as an Array
473
     *
474
     * @return array
475
     * @throws DeepLException
476
     */
477 1
    public function languages()
478
    {
479 1
        $body      = '';
480 1
        $url       = $this->buildBaseUrl(self::API_URL_RESOURCE_LANGUAGES);
481 1
        $languages = $this->request($url, $body);
482
483 1
        return $languages;
484
    }
485
}
486