Completed
Pull Request — master (#16)
by
unknown
02:31
created

DeepL::usage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 1 Features 2
Metric Value
eloc 4
c 2
b 1
f 2
dl 0
loc 7
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 0
crap 2
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 9
        'IT',
136
        'NL',
137 9
        'PL',
138 9
        'RU',
139 9
        'JA',
140
        'ZH',
141 9
    );
142 9
143
    /**
144
     * DeepL API Version (v2 is default since 2018)
145
     *
146
     * @var integer
147 9
     */
148
    protected $apiVersion;
149 9
150 9
    /**
151
     * DeepL API Auth Key (DeepL Pro access required)
152 9
     *
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 1
     */
169
    protected $host;
170
171
    /**
172
     * DeepL constructor
173
     *
174
     * @param string  $authKey
175
     * @param integer $apiVersion
176
     * @param string  $host
177 1
     */
178
    public function __construct($authKey, $apiVersion = 2, $host = 'api.deepl.com')
179
    {
180 1
        $this->authKey    = $authKey;
181 1
        $this->apiVersion = $apiVersion;
182
        $this->host       = $host;
183
        $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 1
185
        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
    }
187
188
    /**
189
     * DeepL destructor
190
     */
191
    public function __destruct()
192
    {
193
        if ($this->curl && is_resource($this->curl)) {
194
            curl_close($this->curl);
195
        }
196
    }
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 4
     * @param string          $formality
207
     * @param string          $resource
208 4
     * @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 4
     * @param array|null      $nonSplittingTags
211 1
     * @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 1
     * @param array|null      $splittingTags
213
     *
214
     * @return mixed
215
     * @throws DeepLException
216 3
     *
217
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
218 3
     */
219 1
    public function translate(
220 1
        $text,
221
        $sourceLanguage = 'de',
222
        $destinationLanguage = 'en',
223
        $tagHandling = null,
224 2
        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
        $this->checkLanguages($sourceLanguage, $destinationLanguage);
235
236
        // build the DeepL API request url
237
        $url = $this->buildUrl(
238 5
            $sourceLanguage,
239
            $destinationLanguage,
240
            $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
            $ignoreTags,
242
            $formality,
243
            $resource,
244
            $splitSentences,
245
            $preserveFormatting,
246 5
            $nonSplittingTags,
247 5
            $outlineDetection,
248 2
            $splittingTags
249 2
        );
250 3
251 3
        $body = $this->buildBody($text);
252 3
253
        // request the DeepL API
254
        $translationsArray = $this->request($url, $body);
255
        $translationsCount = count($translationsArray['translations']);
256
257 5
        if ($translationsCount === 0) {
258 5
            throw new DeepLException('No translations found.');
259 5
        } elseif ($translationsCount === 1) {
260
            return $translationsArray['translations'][0]['text'];
261 5
        }
262 2
263
        return $translationsArray['translations'];
264
    }
265 5
266 2
    /**
267
     * Check if the given languages are supported
268
     *
269 5
     * @param string $sourceLanguage
270 5
     * @param string $destinationLanguage
271
     *
272
     * @return boolean
273 5
     *
274
     * @throws DeepLException
275
     */
276
    protected function checkLanguages($sourceLanguage, $destinationLanguage)
277
    {
278
        $sourceLanguage = strtoupper($sourceLanguage);
279
280
        if (false === in_array($sourceLanguage, $this->sourceLanguages)) {
281
            throw new DeepLException(
282
                sprintf('The language "%s" is not supported as source language.', $sourceLanguage)
283 2
            );
284
        }
285 2
286 2
        $destinationLanguage = strtoupper($destinationLanguage);
287
288 2
        if (false === in_array($destinationLanguage, $this->destinationLanguages)) {
289 2
            throw new DeepLException(
290
                sprintf('The language "%s" is not supported as destination language.', $destinationLanguage)
291
            );
292 2
        }
293 2
294
        return true;
295 2
    }
296 2
297
    /**
298
     * Build the URL for the DeepL API request
299
     *
300 2
     * @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 1
     */
313
    protected function buildUrl(
314 1
        $sourceLanguage = null,
315 1
        $destinationLanguage = null,
316 1
        $tagHandling = null,
317
        array $ignoreTags = null,
318 1
        $formality = 'default',
319
        $resource = 'translate',
320 1
        $splitSentences = null,
321
        $preserveFormatting = null,
322
        array $nonSplittingTags = null,
323
        $outlineDetection = null,
324 1
        array $splittingTags = null
325
    ) {
326 1
        $url = $this->buildBaseUrl($resource);
327 1
328
        if (false === empty($sourceLanguage)) {
329
            $url .= '&'.sprintf(self::API_URL_SOURCE_LANG, strtolower($sourceLanguage));
330
        }
331
332
        if (false === empty($destinationLanguage)) {
333
            $url .= '&'.sprintf(self::API_URL_DESTINATION_LANG, strtolower($destinationLanguage));
334
        }
335
336
        if (false === empty($tagHandling)) {
337
            $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
        }
339
340
        if (false === empty($ignoreTags)) {
341
            $url .= '&'.sprintf(self::API_URL_IGNORE_TAGS, implode(',', $ignoreTags));
342
        }
343
344
        if (false === empty($formality)) {
345
            $url .= '&'.sprintf(self::API_URL_FORMALITY, $formality);
346
        }
347
348
        if (false === empty($splitSentences)) {
349
            $url .= '&'.sprintf(self::API_URL_SPLIT_SENTENCES, $splitSentences);
350
        }
351
352
        if (false === empty($preserveFormatting)) {
353
            $url .= '&'.sprintf(self::API_URL_PRESERVE_FORMATTING, $preserveFormatting);
354
        }
355
356
        if (false === empty($nonSplittingTags)) {
357
            $url .= '&'.sprintf(self::API_URL_NON_SPLITTING_TAGS, implode(',', $nonSplittingTags));
358
        }
359
360
        if (false === empty($outlineDetection)) {
361
            $url .= '&'.sprintf(self::API_URL_OUTLINE_DETECTION, $outlineDetection);
362
        }
363
364
        if (false === empty($splittingTags)) {
365
            $url .= '&'.sprintf(self::API_URL_SPLITTING_TAGS, implode(',', $splittingTags));
366
        }
367
368
        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
    protected function buildBaseUrl($resource = 'translate')
379
    {
380
        $url = sprintf(
381
            self::API_URL_BASE,
382
            self::API_URL_SCHEMA,
383
            $this->host,
384
            $this->apiVersion,
385
            $resource,
386
            $this->authKey
387
        );
388
389
        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
    protected function buildBody($text)
400
    {
401
        $body  = '';
402
        $first = true;
403
404
        if (!is_array($text)) {
405
            $text = (array)$text;
406
        }
407
408
        foreach ($text as $textElement) {
409
            $body .= ($first ? '' : '&').sprintf(self::API_URL_TEXT, rawurlencode($textElement));
410
411
            if ($first) {
412
                $first = false;
413
            }
414
        }
415
416
        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
    protected function request($url, $body)
429
    {
430
        curl_setopt($this->curl, CURLOPT_POST, true);
431
        curl_setopt($this->curl, CURLOPT_URL, $url);
432
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
433
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
434
435
        $response = curl_exec($this->curl);
436
437
        if (curl_errno($this->curl)) {
438
            throw new DeepLException('There was a cURL Request Error.');
439
        }
440
441
        $httpCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
442
443
        if ($httpCode != 200 && array_key_exists($httpCode, $this->errorCodes)) {
444
            throw new DeepLException($this->errorCodes[$httpCode], $httpCode);
445
        }
446
447
        $translationsArray = json_decode($response, true);
448
449
        if (!$translationsArray) {
450
            throw new DeepLException('The Response seems to not be valid JSON.');
451
        }
452
453
        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
    public function usage()
463
    {
464
        $body  = '';
465
        $url   = $this->buildBaseUrl(self::API_URL_RESOURCE_USAGE);
466
        $usage = $this->request($url, $body);
467
468
        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
    public function languages()
478
    {
479
        $body      = '';
480
        $url       = $this->buildBaseUrl(self::API_URL_RESOURCE_LANGUAGES);
481
        $languages = $this->request($url, $body);
482
483
        return $languages;
484
    }
485
}
486