Passed
Push — master ( 214af7...d70e3a )
by Arkadius
01:46
created

DeepL   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 308
Duplicated Lines 0 %

Test Coverage

Coverage 92.68%

Importance

Changes 6
Bugs 0 Features 1
Metric Value
wmc 25
eloc 102
c 6
b 0
f 1
dl 0
loc 308
ccs 76
cts 82
cp 0.9268
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A request() 0 25 5
A __construct() 0 7 1
A __destruct() 0 4 3
A translate() 0 25 3
A checkLanguages() 0 19 3
A buildBody() 0 18 5
A buildUrl() 0 31 5
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
     * DeepL HTTP error codes
54
     *
55
     * @var array
56
     */
57
    protected $errorCodes = array(
58
        400 => 'Wrong request, please check error message and your parameters.',
59
        403 => 'Authorization failed. Please supply a valid auth_key parameter.',
60
        413 => 'Request Entity Too Large. The request size exceeds the current limit.',
61
        429 => 'Too many requests. Please wait and send your request once again.',
62
        456 => 'Quota exceeded. The character limit has been reached.'
63
    );
64
65
    /**
66
     * Supported translation source languages
67
     *
68
     * @var array
69
     */
70
    protected $sourceLanguages = array(
71
        'EN',
72
        'DE',
73
        'FR',
74
        'ES',
75
        'PT',
76
        'IT',
77
        'NL',
78
        'PL',
79
        'RU'
80
    );
81
82
    /**
83
     * Supported translation destination languages
84
     *
85
     * @var array
86
     */
87
    protected $destinationLanguages = array(
88
        'EN',
89
        'DE',
90
        'FR',
91
        'ES',
92
        'PT',
93
        'IT',
94
        'NL',
95
        'PL',
96
        'RU'
97
    );
98
99
    /**
100
     * @var integer
101
     */
102
    protected $apiVersion;
103
104
    /**
105
     * DeepL API Auth Key (DeepL Pro access required)
106
     *
107
     * @var string
108
     */
109
    protected $authKey;
110
111
    /**
112
     * cURL resource
113
     *
114
     * @var resource
115
     */
116
    protected $curl;
117
118
    /**
119
     * DeepL constructor
120
     *
121
     * @param string  $authKey
122
     * @param integer $apiVersion
123
     */
124 11
    public function __construct($authKey, $apiVersion = 1)
125
    {
126 11
        $this->authKey    = $authKey;
127 11
        $this->apiVersion = $apiVersion;
128 11
        $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...
129
130 11
        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

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