Passed
Pull Request — master (#11)
by
unknown
03:27
created

DeepL   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 321
Duplicated Lines 0 %

Test Coverage

Coverage 79.73%

Importance

Changes 7
Bugs 0 Features 2
Metric Value
wmc 26
eloc 105
c 7
b 0
f 2
dl 0
loc 321
ccs 59
cts 74
cp 0.7973
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
    );
86
87
    /**
88
     * Supported translation destination languages
89
     *
90
     * @var array
91
     */
92
    protected $destinationLanguages = array(
93
        'EN',
94
        'DE',
95
        'FR',
96
        'ES',
97
        'PT',
98
        'IT',
99
        'NL',
100
        'PL',
101
        'RU'
102
    );
103
104
    /**
105
     * @var integer
106
     */
107
    protected $apiVersion;
108
109
    /**
110
     * DeepL API Auth Key (DeepL Pro access required)
111
     *
112
     * @var string
113
     */
114
    protected $authKey;
115
116
    /**
117
     * cURL resource
118
     *
119
     * @var resource
120
     */
121
    protected $curl;
122
123
    /**
124
     * DeepL constructor
125
     *
126
     * @param string  $authKey
127
     * @param integer $apiVersion
128
     */
129 7
    public function __construct($authKey, $apiVersion = 1)
130
    {
131 7
        $this->authKey    = $authKey;
132 7
        $this->apiVersion = $apiVersion;
133 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...
134
135 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

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