Passed
Push — master ( 570349...545464 )
by
unknown
04:00 queued 12s
created

DeepL::buildBody()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 18
ccs 13
cts 13
cp 1
rs 9.6111
cc 5
nc 10
nop 1
crap 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
     * 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
        'PT-PT',
101
        'PT-BR',
102
        'IT',
103
        'NL',
104
        'PL',
105
        'RU',
106
        'JA',
107
        'ZH'
108
    );
109
110
    /**
111
     * @var integer
112
     */
113
    protected $apiVersion;
114
115
    /**
116
     * DeepL API Auth Key (DeepL Pro access required)
117
     *
118
     * @var string
119
     */
120
    protected $authKey;
121
122
    /**
123
     * cURL resource
124
     *
125
     * @var resource
126
     */
127
    protected $curl;
128
129
    /**
130
     * DeepL constructor
131
     *
132
     * @param string  $authKey
133
     * @param integer $apiVersion
134
     */
135 11
    public function __construct($authKey, $apiVersion = 1)
136
    {
137 11
        $this->authKey    = $authKey;
138 11
        $this->apiVersion = $apiVersion;
139 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...
140
141 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

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