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

DeepL::translate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 45
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 3.0067

Importance

Changes 5
Bugs 1 Features 3
Metric Value
eloc 21
c 5
b 1
f 3
dl 0
loc 45
ccs 20
cts 22
cp 0.9091
rs 9.584
cc 3
nc 3
nop 12
crap 3.0067

How to fix   Many Parameters   

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
     * API URL: Parameter text
36
     */
37
    const API_URL_TEXT = 'text=%s';
38
39
    /**
40
     * API URL: Parameter source_lang
41
     */
42
    const API_URL_SOURCE_LANG = 'source_lang=%s';
43
44
    /**
45
     * API URL: Parameter target_lang
46
     */
47
    const API_URL_DESTINATION_LANG = 'target_lang=%s';
48
49
    /**
50
     * API URL: Parameter tag_handling
51
     */
52
    const API_URL_TAG_HANDLING = 'tag_handling=%s';
53
54
    /**
55
     * API URL: Parameter ignore_tags
56
     */
57
    const API_URL_IGNORE_TAGS = 'ignore_tags=%s';
58
59
    /**
60
     * API URL: Parameter formality
61
     */
62
    const API_URL_FORMALITY = 'formality=%s';
63
64
    /**
65
     * API URL: Parameter split_sentences
66
     */
67
    const API_URL_SPLIT_SENTENCES = 'split_sentences=%s';
68
69
    /**
70
     * API URL: Parameter preserve_formatting
71
     */
72
    const API_URL_PRESERVE_FORMATTING = 'preserve_formatting=%s';
73
74
    /**
75
     * API URL: Parameter non_splitting_tags
76
     */
77
    const API_URL_NON_SPLITTING_TAGS = 'non_splitting_tags=%s';
78
79
    /**
80
     * API URL: Parameter outline_detection
81
     */
82
    const API_URL_OUTLINE_DETECTION = 'outline_detection=%s';
83
84
    /**
85
     * API URL: Parameter splitting_tags
86
     */
87
    const API_URL_SPLITTING_TAGS = 'splitting_tags=%s';
88
89
    /**
90
     * Supported translation source languages
91
     *
92
     * @var array
93
     */
94
    protected $sourceLanguages = array(
95
        'EN',
96
        'DE',
97
        'FR',
98
        'ES',
99
        'PT',
100
        'IT',
101
        'NL',
102
        'PL',
103
        'RU',
104
        'JA',
105
        'ZH',
106
    );
107
108
    /**
109
     * Supported translation destination languages
110
     *
111
     * @var array
112
     */
113
    protected $destinationLanguages = array(
114
        'EN',
115
        'DE',
116
        'FR',
117
        'ES',
118
        'PT',
119
        'PT-PT',
120
        'PT-BR',
121
        'IT',
122
        'NL',
123
        'PL',
124
        'RU',
125
        'JA',
126
        'ZH',
127
    );
128
129
    /**
130
     * DeepL API Version (v2 is default since 2018)
131
     *
132
     * @var integer
133
     */
134
    protected $apiVersion;
135
136
    /**
137
     * DeepL API Auth Key (DeepL Pro access required)
138
     *
139
     * @var string
140
     */
141
    protected $authKey;
142
143
    /**
144
     * cURL resource
145
     *
146
     * @var resource
147
     */
148
    protected $curl;
149
150
    /**
151
     * Hostname of the API (in most cases api.deepl.com)
152
     *
153
     * @var string
154
     */
155
    protected $host;
156
157
    /**
158
     * DeepL constructor
159
     *
160
     * @param string  $authKey
161
     * @param integer $apiVersion
162
     * @param string  $host
163
     */
164 22
    public function __construct($authKey, $apiVersion = 2, $host = 'api.deepl.com')
165
    {
166 22
        $this->authKey    = $authKey;
167 22
        $this->apiVersion = $apiVersion;
168 22
        $this->host       = $host;
169 22
        $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...
170
171 22
        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

171
        curl_setopt(/** @scrutinizer ignore-type */ $this->curl, CURLOPT_RETURNTRANSFER, 1);
Loading history...
172 22
    }
173
174
    /**
175
     * DeepL destructor
176
     */
177 22
    public function __destruct()
178
    {
179 22
        if ($this->curl && is_resource($this->curl)) {
180 22
            curl_close($this->curl);
181
        }
182 22
    }
183
184
    /**
185
     * Translate the text string or array from source to destination language
186
     *
187
     * @param string|string[] $text
188
     * @param string          $sourceLanguage
189
     * @param string          $destinationLanguage
190
     * @param string          $tagHandling
191
     * @param array|null      $ignoreTags
192
     * @param string          $formality
193
     * @param string          $resource
194
     * @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...
195
     * @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...
196
     * @param array|null      $nonSplittingTags
197
     * @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...
198
     * @param array|null      $splittingTags
199
     *
200
     * @return mixed
201
     * @throws DeepLException
202
     *
203
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
204
     */
205 9
    public function translate(
206
        $text,
207
        $sourceLanguage = 'de',
208
        $destinationLanguage = 'en',
209
        $tagHandling = null,
210
        array $ignoreTags = null,
211
        $formality = "default",
212
        $resource = self::API_URL_RESOURCE_TRANSLATE,
213
        $splitSentences = null,
214
        $preserveFormatting = null,
215
        array $nonSplittingTags = null,
216
        $outlineDetection = null,
217
        array $splittingTags = null
218
    ) {
219
        // make sure we only accept supported languages
220 9
        $this->checkLanguages($sourceLanguage, $destinationLanguage);
221
222
        // build the DeepL API request url
223 9
        $url = $this->buildUrl(
224 9
            $sourceLanguage,
225 9
            $destinationLanguage,
226 9
            $tagHandling,
227 9
            $ignoreTags,
228 9
            $formality,
229 9
            $resource,
230 9
            $splitSentences,
231 9
            $preserveFormatting,
232 9
            $nonSplittingTags,
233 9
            $outlineDetection,
234 9
            $splittingTags
235
        );
236
237 9
        $body = $this->buildBody($text);
238
239
        // request the DeepL API
240 9
        $translationsArray = $this->request($url, $body);
241 6
        $translationsCount = count($translationsArray['translations']);
242
243 6
        if ($translationsCount === 0) {
244
            throw new DeepLException('No translations found.');
245 6
        } elseif ($translationsCount === 1) {
246 6
            return $translationsArray['translations'][0]['text'];
247
        }
248
249
        return $translationsArray['translations'];
250
    }
251
252
    /**
253
     * Check if the given languages are supported
254
     *
255
     * @param string $sourceLanguage
256
     * @param string $destinationLanguage
257
     *
258
     * @return boolean
259
     *
260
     * @throws DeepLException
261
     */
262 12
    protected function checkLanguages($sourceLanguage, $destinationLanguage)
263
    {
264 12
        $sourceLanguage = strtoupper($sourceLanguage);
265
266 12
        if (false === in_array($sourceLanguage, $this->sourceLanguages)) {
267 1
            throw new DeepLException(
268 1
                sprintf('The language "%s" is not supported as source language.', $sourceLanguage)
269
            );
270
        }
271
272 11
        $destinationLanguage = strtoupper($destinationLanguage);
273
274 11
        if (false === in_array($destinationLanguage, $this->destinationLanguages)) {
275 1
            throw new DeepLException(
276 1
                sprintf('The language "%s" is not supported as destination language.', $destinationLanguage)
277
            );
278
        }
279
280 10
        return true;
281
    }
282
283
    /**
284
     * Build the URL for the DeepL API request
285
     *
286
     * @param string     $sourceLanguage
287
     * @param string     $destinationLanguage
288
     * @param string     $tagHandling
289
     * @param array|null $ignoreTags
290
     * @param string     $formality
291
     * @param string     $resource
292
     * @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...
293
     * @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...
294
     * @param array|null $nonSplittingTags
295
     * @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...
296
     * @param array|null $splittingTags
297
     *
298
     * @return string
299
     *
300
     * @SuppressWarnings(PHPMD.NPathComplexity)
301
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
302
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
303
     */
304 14
    protected function buildUrl(
305
        $sourceLanguage = null,
306
        $destinationLanguage = null,
307
        $tagHandling = null,
308
        array $ignoreTags = null,
309
        $formality = 'default',
310
        $resource = self::API_URL_RESOURCE_TRANSLATE,
311
        $splitSentences = null,
312
        $preserveFormatting = null,
313
        array $nonSplittingTags = null,
314
        $outlineDetection = 1,
315
        array $splittingTags = null
316
    ) {
317 14
        $url = $this->buildBaseUrl($resource);
318
319 14
        if (false === empty($sourceLanguage)) {
320 14
            $url .= '&'.sprintf(self::API_URL_SOURCE_LANG, strtolower($sourceLanguage));
321
        }
322
323 14
        if (false === empty($destinationLanguage)) {
324 14
            $url .= '&'.sprintf(self::API_URL_DESTINATION_LANG, strtolower($destinationLanguage));
325
        }
326
327 14
        if (false === empty($tagHandling)) {
328 6
            $url .= '&'.sprintf(self::API_URL_TAG_HANDLING, $tagHandling);
329
        }
330
331 14
        if (false === empty($ignoreTags)) {
332 5
            $url .= '&'.sprintf(self::API_URL_IGNORE_TAGS, urlencode(implode(',', $ignoreTags)));
333
        }
334
335 14
        if (false === empty($formality)) {
336 14
            $url .= '&'.sprintf(self::API_URL_FORMALITY, $formality);
337
        }
338
339 14
        if (false === empty($splitSentences)) {
340 2
            $url .= '&'.sprintf(self::API_URL_SPLIT_SENTENCES, $splitSentences);
341
        }
342
343 14
        if (false === empty($preserveFormatting)) {
344 2
            $url .= '&'.sprintf(self::API_URL_PRESERVE_FORMATTING, $preserveFormatting);
345
        }
346
347 14
        if (false === empty($nonSplittingTags)) {
348 2
            $url .= '&'.sprintf(self::API_URL_NON_SPLITTING_TAGS, urlencode(implode(',', $nonSplittingTags)));
349
        }
350
351 14
        if (0 === $outlineDetection) {
352 2
            $url .= '&'.sprintf(self::API_URL_OUTLINE_DETECTION, $outlineDetection);
353
        }
354
355 14
        if (false === empty($splittingTags)) {
356 2
            $url .= '&'.sprintf(self::API_URL_SPLITTING_TAGS, urlencode(implode(',', $splittingTags)));
357
        }
358
359 14
        return $url;
360
    }
361
362
    /**
363
     * Creates the Base-Url which all of the 3 API-recourses have in common.
364
     *
365
     * @param string $resource
366
     *
367
     * @return string
368
     */
369 18
    protected function buildBaseUrl($resource = 'translate')
370
    {
371 18
        $url = sprintf(
372 18
            self::API_URL_BASE,
373 18
            self::API_URL_SCHEMA,
374 18
            $this->host,
375 18
            $this->apiVersion,
376 18
            $resource,
377 18
            $this->authKey
378
        );
379
380 18
        return $url;
381
    }
382
383
    /**
384
     * Build the body for the DeepL API request
385
     *
386
     * @param string|string[] $text
387
     *
388
     * @return string
389
     */
390 10
    protected function buildBody($text)
391
    {
392 10
        $body  = '';
393 10
        $first = true;
394
395 10
        if (!is_array($text)) {
396 10
            $text = (array)$text;
397
        }
398
399 10
        foreach ($text as $textElement) {
400 10
            $body .= ($first ? '' : '&').sprintf(self::API_URL_TEXT, rawurlencode($textElement));
401
402 10
            if ($first) {
403 10
                $first = false;
404
            }
405
        }
406
407 10
        return $body;
408
    }
409
410
    /**
411
     * Make a request to the given URL
412
     *
413
     * @param string $url
414
     *
415
     * @return array
416
     *
417
     * @throws DeepLException
418
     */
419 11
    protected function request($url, $body)
420
    {
421 11
        curl_setopt($this->curl, CURLOPT_POST, true);
422 11
        curl_setopt($this->curl, CURLOPT_URL, $url);
423 11
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
424 11
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
425
426 11
        $response = curl_exec($this->curl);
427
428 11
        if (curl_errno($this->curl)) {
429
            throw new DeepLException('There was a cURL Request Error.');
430
        }
431 11
        $httpCode      = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
432 11
        $responseArray = json_decode($response, true);
433
434 11
        if ($httpCode != 200) {
435 3
            throw new DeepLException($responseArray['message'], $httpCode);
436
        }
437
438 8
        if (false === is_array($responseArray)) {
439
            throw new DeepLException('The Response seems to not be valid JSON.');
440
        }
441
442 8
        return $responseArray;
443
    }
444
445
    /**
446
     * Calls the usage-Endpoint and return Json-response as an array
447
     *
448
     * @return array
449
     * @throws DeepLException
450
     */
451 1
    public function usage()
452
    {
453 1
        $body  = '';
454 1
        $url   = $this->buildBaseUrl(self::API_URL_RESOURCE_USAGE);
455 1
        $usage = $this->request($url, $body);
456
457 1
        return $usage;
458
    }
459
460
    /**
461
     * Call languages-Endpoint and return Json-response as an Array
462
     *
463
     * @return array
464
     * @throws DeepLException
465
     */
466 1
    public function languages()
467
    {
468 1
        $body      = '';
469 1
        $url       = $this->buildBaseUrl(self::API_URL_RESOURCE_LANGUAGES);
470 1
        $languages = $this->request($url, $body);
471
472 1
        return $languages;
473
    }
474
}
475