Completed
Push — master ( 53d599...570349 )
by
unknown
14s queued 12s
created

DeepL   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Test Coverage

Coverage 81.18%

Importance

Changes 8
Bugs 0 Features 2
Metric Value
wmc 26
eloc 109
c 8
b 0
f 2
dl 0
loc 325
ccs 69
cts 85
cp 0.8118
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A checkLanguages() 0 19 3
A request() 0 25 5
A __construct() 0 7 1
A buildBody() 0 18 5
A buildUrl() 0 36 6
A __destruct() 0 4 3
A translate() 0 26 3
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
    /**
13
     * API v1 URL
14
     */
15
    const API_URL_V1               = 'https://api.deepl.com/v1/translate';
16
17
    /**
18
     * API v2 URL
19
     */
20
    const API_URL_V2               = 'https://api.deepl.com/v2/translate';
21
22
    /**
23
     * API URL: Parameter auth_key
24
     */
25
    const API_URL_AUTH_KEY         = 'auth_key=%s';
26
27
    /**
28
     * API URL: Parameter text
29
     */
30
    const API_URL_TEXT             = 'text=%s';
31
32
    /**
33
     * API URL: Parameter source_lang
34
     */
35
    const API_URL_SOURCE_LANG      = 'source_lang=%s';
36
37
    /**
38
     * API URL: Parameter target_lang
39
     */
40
    const API_URL_DESTINATION_LANG = 'target_lang=%s';
41
42
    /**
43
     * API URL: Parameter tag_handling
44
     */
45
    const API_URL_TAG_HANDLING     = 'tag_handling=%s';
46
47
    /**
48
     * API URL: Parameter ignore_tags
49
     */
50
    const API_URL_IGNORE_TAGS      = 'ignore_tags=%s';
51
52
    /**
53
     * API URL: Parameter formality
54
     */
55
    const API_URL_FORMALITY        = 'formality=%s';
56
57
    /**
58
     * DeepL HTTP error codes
59
     *
60
     * @var array
61
     */
62
    protected $errorCodes = array(
63
        400 => 'Wrong request, please check error message and your parameters.',
64
        403 => 'Authorization failed. Please supply a valid auth_key parameter.',
65
        413 => 'Request Entity Too Large. The request size exceeds the current limit.',
66
        429 => 'Too many requests. Please wait and send your request once again.',
67
        456 => 'Quota exceeded. The character limit has been reached.'
68
    );
69
70
    /**
71
     * Supported translation source languages
72
     *
73
     * @var array
74
     */
75
    protected $sourceLanguages = array(
76
        'EN',
77
        'DE',
78
        'FR',
79
        'ES',
80
        'PT',
81
        'IT',
82
        'NL',
83
        'PL',
84
        'RU',
85
        'JA',
86
        'ZH'
87
    );
88
89
    /**
90
     * Supported translation destination languages
91
     *
92
     * @var array
93
     */
94
    protected $destinationLanguages = 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
     * @var integer
110
     */
111
    protected $apiVersion;
112
113
    /**
114
     * DeepL API Auth Key (DeepL Pro access required)
115
     *
116
     * @var string
117
     */
118
    protected $authKey;
119
120
    /**
121
     * cURL resource
122
     *
123
     * @var resource
124
     */
125
    protected $curl;
126
127
    /**
128
     * DeepL constructor
129
     *
130
     * @param string  $authKey
131
     * @param integer $apiVersion
132
     */
133 7
    public function __construct($authKey, $apiVersion = 1)
134
    {
135 7
        $this->authKey    = $authKey;
136 7
        $this->apiVersion = $apiVersion;
137 7
        $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...
138
139 7
        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

139
        curl_setopt(/** @scrutinizer ignore-type */ $this->curl, CURLOPT_RETURNTRANSFER, 1);
Loading history...
140 7
    }
141
142
    /**
143
     * DeepL destructor
144
     */
145 7
    public function __destruct()
146
    {
147 7
        if ($this->curl && is_resource($this->curl)) {
148 7
            curl_close($this->curl);
149 7
        }
150 7
    }
151
152
    /**
153
     * Translate the text string or array from source to destination language
154
     *
155
     * @param string|string[] $text
156
     * @param string          $sourceLanguage
157
     * @param string          $destinationLanguage
158
     * @param array           $tagHandling
159
     * @param array           $ignoreTags
160
     * @param string          $formality
161
     *
162
     * @return string|string[]
163
     *
164
     * @throws DeepLException
165
     */
166 1
    public function translate(
167
        $text,
168
        $sourceLanguage = 'de',
169
        $destinationLanguage = 'en',
170
        array $tagHandling = array(),
171
        array $ignoreTags = array(),
172
        $formality = "default"
173
    ) {
174
        // make sure we only accept supported languages
175 1
        $this->checkLanguages($sourceLanguage, $destinationLanguage);
176
177
        // build the DeepL API request url
178 1
        $url  = $this->buildUrl($sourceLanguage, $destinationLanguage, $tagHandling, $ignoreTags, $formality);
179 1
        $body = $this->buildBody($text);
180
181
        // request the DeepL API
182 1
        $translationsArray = $this->request($url, $body);
183
        $translationsCount = count($translationsArray['translations']);
184
185
        if ($translationsCount == 0) {
186
            throw new DeepLException('No translations found.');
187
        } elseif ($translationsCount == 1) {
188
            return $translationsArray['translations'][0]['text'];
189
        }
190
191
        return $translationsArray['translations'];
192
    }
193
194
    /**
195
     * Check if the given languages are supported
196
     *
197
     * @param string $sourceLanguage
198
     * @param string $destinationLanguage
199
     *
200
     * @return boolean
201
     *
202
     * @throws DeepLException
203
     */
204 4
    protected function checkLanguages($sourceLanguage, $destinationLanguage)
205
    {
206 4
        $sourceLanguage = strtoupper($sourceLanguage);
207
208 4
        if (!in_array($sourceLanguage, $this->sourceLanguages)) {
209 1
            throw new DeepLException(
210 1
                sprintf('The language "%s" is not supported as source language.', $sourceLanguage)
211 1
            );
212
        }
213
214 3
        $destinationLanguage = strtoupper($destinationLanguage);
215
216 3
        if (!in_array($destinationLanguage, $this->destinationLanguages)) {
217 1
            throw new DeepLException(
218 1
                sprintf('The language "%s" is not supported as destination language.', $destinationLanguage)
219 1
            );
220
        }
221
222 2
        return true;
223
    }
224
225
    /**
226
     * Build the URL for the DeepL API request
227
     *
228
     * @param string $sourceLanguage
229
     * @param string $destinationLanguage
230
     * @param array  $tagHandling
231
     * @param array  $ignoreTags
232
     * @param string $formality
233
     *
234
     * @return string
235
     */
236 3
    protected function buildUrl(
237
        $sourceLanguage,
238
        $destinationLanguage,
239
        array $tagHandling = array(),
240
        array $ignoreTags = array(),
241
        $formality = "default"
242
    ) {
243
        // select correct api url
244 3
        switch ($this->apiVersion) {
245 3
            case 1:
246 3
                $url = DeepL::API_URL_V1;
247 3
                break;
248
            case 2:
249
                $url = DeepL::API_URL_V2;
250
                break;
251
            default:
252
                $url = DeepL::API_URL_V1;
253 3
        }
254
255 3
        $url .= '?' . sprintf(DeepL::API_URL_AUTH_KEY, $this->authKey);
256 3
        $url .= '&' . sprintf(DeepL::API_URL_SOURCE_LANG, strtolower($sourceLanguage));
257 3
        $url .= '&' . sprintf(DeepL::API_URL_DESTINATION_LANG, strtolower($destinationLanguage));
258
259 3
        if (!empty($tagHandling)) {
260 1
            $url .= '&' . sprintf(DeepL::API_URL_TAG_HANDLING, implode(',', $tagHandling));
261 1
        }
262
263 3
        if (!empty($ignoreTags)) {
264 1
            $url .= '&' . sprintf(DeepL::API_URL_IGNORE_TAGS, implode(',', $ignoreTags));
265 1
        }
266
267 3
        if (!empty($formality)) {
268 3
            $url .= '&' . sprintf(DeepL::API_URL_FORMALITY, $formality);
269 3
        }
270
271 3
        return $url;
272
    }
273
274
    /**
275
     * Build the body for the DeepL API request
276
     *
277
     * @param string|string[] $text
278
     *
279
     * @return string
280
     */
281 2
    protected function buildBody($text)
282
    {
283 2
        $body  = '';
284 2
        $first = true;
285
286 2
        if (!is_array($text)) {
287 2
            $text = (array)$text;
288 2
        }
289
290 2
        foreach ($text as $textElement) {
291 2
            $body .= ($first ? '' : '&') . sprintf(DeepL::API_URL_TEXT, rawurlencode($textElement));
292
293 2
            if ($first) {
294 2
                $first = false;
295 2
            }
296 2
        }
297
298 2
        return $body;
299
    }
300
301
    /**
302
     * Make a request to the given URL
303
     *
304
     * @param string $url
305
     *
306
     * @return array
307
     *
308
     * @throws DeepLException
309
     */
310 1
    protected function request($url, $body)
311
    {
312 1
        curl_setopt($this->curl, CURLOPT_URL, $url);
313 1
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
314 1
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
315
316 1
        $response = curl_exec($this->curl);
317
318 1
        if (curl_errno($this->curl)) {
319
            throw new DeepLException('There was a cURL Request Error.');
320
        }
321
322 1
        $httpCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
323
324 1
        if ($httpCode != 200 && array_key_exists($httpCode, $this->errorCodes)) {
325 1
            throw new DeepLException($this->errorCodes[$httpCode], $httpCode);
326
        }
327
328
        $translationsArray = json_decode($response, true);
329
330
        if (!$translationsArray) {
331
            throw new DeepLException('The Response seems to not be valid JSON.');
332
        }
333
334
        return $translationsArray;
335
    }
336
}
337