Passed
Pull Request — master (#20)
by
unknown
02:38
created

DeepL::languages()   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 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 7
ccs 0
cts 5
cp 0
rs 10
cc 1
nc 1
nop 1
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: usage
21
     */
22
    const API_URL_RESOURCE_USAGE = 'usage';
23
24
    /**
25
     * API URL: languages
26
     */
27
    const API_URL_RESOURCE_LANGUAGES = 'languages';
28
29
    /**
30
     * DeepL API Version (v2 is default since 2018)
31
     *
32
     * @var integer
33
     */
34
    protected $apiVersion;
35
36
    /**
37
     * DeepL API Auth Key (DeepL Pro access required)
38
     *
39
     * @var string
40
     */
41
    protected $authKey;
42
43
    /**
44
     * cURL resource
45
     *
46
     * @var resource
47
     */
48
    protected $curl;
49
50
    /**
51
     * Hostname of the API (in most cases api.deepl.com)
52
     *
53
     * @var string
54
     */
55
    protected $host;
56
57
    /**
58
     * URL of the proxy used to connect to DeepL (if needed)
59
     *
60
     * @var string|null
61
     */
62
    protected $proxy = null;
63
64
    /**
65
     * Credentials for the proxy used to connect to DeepL (username:password)
66
     *
67
     * @var string|null
68
     */
69
    protected $proxyCredentials = null;
70
71
    /**
72
     * Maximum number of seconds the query should take
73
     *
74
     * @var int|null
75
     */
76
    protected $timeout = null;
77
78
    /**
79
     * DeepL constructor
80
     *
81
     * @param string  $authKey
82
     * @param integer $apiVersion
83
     * @param string  $host
84
     */
85 16
    public function __construct($authKey, $apiVersion = 2, $host = 'api.deepl.com')
86
    {
87 16
        $this->authKey    = $authKey;
88 16
        $this->apiVersion = $apiVersion;
89 16
        $this->host       = $host;
90 16
        $this->curl       = curl_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like curl_init() can also be of type CurlHandle. 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...
91
92 16
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1);
93 16
    }
94
95
    /**
96
     * DeepL destructor
97
     */
98 16
    public function __destruct()
99
    {
100 16
        if ($this->curl && is_resource($this->curl)) {
101 16
            curl_close($this->curl);
102 16
        }
103 16
    }
104
105
    /**
106
     * Call languages-Endpoint and return Json-response as an Array
107
     *
108
     * @param string $type
109
     *
110
     * @return array
111
     * @throws DeepLException
112
     */
113
    public function languages($type = null)
114
    {
115
        $url       = $this->buildBaseUrl(self::API_URL_RESOURCE_LANGUAGES);
116
        $body      = $this->buildQuery(array('type' => $type));
117
        $languages = $this->request($url, $body);
118
119
        return $languages;
120
    }
121
122
    /**
123
     * Set a proxy to use for querying the DeepL API if needed
124
     *
125
     * @param string $proxy Proxy URL (e.g 'http://proxy-domain.com:3128')
126
     */
127
    public function setProxy($proxy)
128
    {
129
130
        $this->proxy = $proxy;
131
    }
132
133
    /**
134
     * Set the proxy credentials
135
     *
136
     * @param string $proxyCredentials proxy credentials (using 'username:password' format)
137
     */
138
    public function setProxyCredentials($proxyCredentials)
139
    {
140
        $this->proxyCredentials = $proxyCredentials;
141
    }
142
143
    /**
144
     * Set a timeout for queries to the DeepL API
145
     *
146
     * @param int $timeout Timeout in seconds
147
     */
148 1
    public function setTimeout($timeout)
149
    {
150 1
        $this->timeout = $timeout;
151 1
    }
152
153
    /**
154
     * Translate the text string or array from source to destination language
155
     * For detailed info on Parameters see README.md
156
     *
157
     * @param string|string[] $text
158
     * @param string          $sourceLang
159
     * @param string          $targetLang
160
     * @param string          $tagHandling
161
     * @param array|null      $ignoreTags
162
     * @param string          $formality
163
     * @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...
164
     * @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...
165
     * @param array|null      $nonSplittingTags
166
     * @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...
167
     * @param array|null      $splittingTags
168
     *
169
     * @return array
170
     * @throws DeepLException
171
     *
172
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
173
     */
174 3
    public function translate(
175
        $text,
176
        $sourceLang = 'de',
177
        $targetLang = 'en',
178
        $tagHandling = null,
179
        array $ignoreTags = null,
180
        $formality = 'default',
181
        $splitSentences = null,
182
        $preserveFormatting = null,
183
        array $nonSplittingTags = null,
184
        $outlineDetection = null,
185
        array $splittingTags = null
186
    ) {
187 3
        if (is_array($tagHandling)) {
0 ignored issues
show
introduced by
The condition is_array($tagHandling) is always false.
Loading history...
188 1
            throw new \InvalidArgumentException('$tagHandling must be of type String in V2 of DeepLLibrary');
189
        }
190
        $paramsArray = array(
191 2
            'text'                => $text,
192 2
            'source_lang'         => $sourceLang,
193 2
            'target_lang'         => $targetLang,
194 2
            'splitting_tags'      => $splittingTags,
195 2
            'non_splitting_tags'  => $nonSplittingTags,
196 2
            'ignore_tags'         => $ignoreTags,
197 2
            'tag_handling'        => $tagHandling,
198 2
            'formality'           => $formality,
199 2
            'split_sentences'     => $splitSentences,
200 2
            'preserve_formatting' => $preserveFormatting,
201 2
            'outline_detection'   => $outlineDetection,
202 2
        );
203
204 2
        $paramsArray = $this->removeEmptyParams($paramsArray);
205 2
        $url         = $this->buildBaseUrl();
206 2
        $body        = $this->buildQuery($paramsArray);
207
208
        // request the DeepL API
209 2
        $translationsArray = $this->request($url, $body);
210
211
        return $translationsArray['translations'];
212
    }
213
214
    /**
215
     * Calls the usage-Endpoint and return Json-response as an array
216
     *
217
     * @return array
218
     * @throws DeepLException
219
     */
220
    public function usage()
221
    {
222
        $url   = $this->buildBaseUrl(self::API_URL_RESOURCE_USAGE);
223
        $usage = $this->request($url);
224
225
        return $usage;
226
    }
227
228
229
    /**
230
     * Creates the Base-Url which all of the 3 API-resources have in common.
231
     *
232
     * @param string $resource
233
     *
234
     * @return string
235
     */
236 4
    protected function buildBaseUrl($resource = 'translate')
237
    {
238 4
        $url = sprintf(
239 4
            self::API_URL_BASE,
240 4
            self::API_URL_SCHEMA,
241 4
            $this->host,
242 4
            $this->apiVersion,
243 4
            $resource,
244 4
            $this->authKey
245 4
        );
246
247 4
        return $url;
248
    }
249
250
    /**
251
     * @param array $paramsArray
252
     *
253
     * @return string
254
     */
255 8
    protected function buildQuery($paramsArray)
256
    {
257 8
        if (isset($paramsArray['text']) && true === is_array($paramsArray['text'])) {
258 1
            $text = $paramsArray['text'];
259 1
            unset($paramsArray['text']);
260 1
            $textString = '';
261 1
            foreach ($text as $textElement) {
262 1
                $textString .= '&text='.rawurlencode($textElement);
263 1
            }
264 1
        }
265
266 8
        foreach ($paramsArray as $key => $value) {
267 8
            if (true === is_array($value)) {
268 4
                $paramsArray[$key] = implode(',', $value);
269 4
            }
270 8
        }
271
272 8
        $body = http_build_query($paramsArray, null, '&');
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $numeric_prefix of http_build_query(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

272
        $body = http_build_query($paramsArray, /** @scrutinizer ignore-type */ null, '&');
Loading history...
273
274 8
        if (isset($textString)) {
275 1
            $body = $textString.'&'.$body;
276 1
        }
277
278 8
        return $body;
279
    }
280
281
282
283
284
    /**
285
     * Make a request to the given URL
286
     *
287
     * @param string $url
288
     * @param string $body
289
     *
290
     * @return array
291
     *
292
     * @throws DeepLException
293
     */
294 2
    protected function request($url, $body = '')
295
    {
296 2
        curl_setopt($this->curl, CURLOPT_POST, true);
297 2
        curl_setopt($this->curl, CURLOPT_URL, $url);
298 2
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
299 2
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
300
301 2
        if ($this->proxy !== null) {
302
            curl_setopt($this->curl, CURLOPT_PROXY, $this->proxy);
303
        }
304
305 2
        if ($this->proxyCredentials !== null) {
306
            curl_setopt($this->curl, CURLOPT_PROXYAUTH, $this->proxyCredentials);
307
        }
308
309 2
        if ($this->timeout !== null) {
310 1
            curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->timeout);
311 1
        }
312
313 2
        $response = curl_exec($this->curl);
314
315 2
        if (curl_errno($this->curl)) {
316 1
            throw new DeepLException('There was a cURL Request Error.');
317
        }
318 1
        $httpCode      = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
319 1
        $responseArray = json_decode($response, true);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $json of json_decode() does only seem to accept string, 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

319
        $responseArray = json_decode(/** @scrutinizer ignore-type */ $response, true);
Loading history...
320
321 1
        if ($httpCode != 200 && is_array($responseArray) && array_key_exists('message', $responseArray)) {
322
            throw new DeepLException($responseArray['message'], $httpCode);
323
        }
324
325 1
        if (false === is_array($responseArray)) {
326 1
            throw new DeepLException('The Response seems to not be valid JSON.', $httpCode);
327
        }
328
329
        return $responseArray;
330
    }
331
332
    /**
333
     * @param array $paramsArray
334
     *
335
     * @return array
336
     */
337 7
    private function removeEmptyParams($paramsArray)
338
    {
339
340 7
        foreach ($paramsArray as $key => $value) {
341 7
            if (true === empty($value)) {
342 7
                unset($paramsArray[$key]);
343 7
            }
344
            // Special Workaround for outline_detection which will be unset above
345
            // DeepL assumes outline_detection=1 if it is not send
346
            // in order to deactivate it, we need to send outline_detection=0 to the api
347 7
            if ('outline_detection' === $key) {
348 7
                if (1 === $value) {
349 1
                    unset($paramsArray[$key]);
350 1
                }
351
352 7
                if (0 === $value) {
353 2
                    $paramsArray[$key] = 0;
354 2
                }
355 7
            }
356 7
        }
357
358 7
        return $paramsArray;
359
    }
360
}
361