Passed
Push — master ( 2ac56f...b68d5d )
by
unknown
02:11 queued 11s
created

DeepL::buildQuery()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 13
c 3
b 1
f 0
dl 0
loc 24
ccs 14
cts 14
cp 1
rs 8.8333
cc 7
nc 12
nop 1
crap 7
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
     * DeepL constructor
59
     *
60
     * @param string  $authKey
61
     * @param integer $apiVersion
62
     * @param string  $host
63
     */
64 30
    public function __construct($authKey, $apiVersion = 2, $host = 'api.deepl.com')
65
    {
66 30
        $this->authKey    = $authKey;
67 30
        $this->apiVersion = $apiVersion;
68 30
        $this->host       = $host;
69 30
        $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...
70
71 30
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1);
72 30
    }
73
74
    /**
75
     * DeepL destructor
76
     */
77 30
    public function __destruct()
78
    {
79 30
        if ($this->curl && is_resource($this->curl)) {
80 30
            curl_close($this->curl);
81
        }
82 30
    }
83
84
    /**
85
     * Call languages-Endpoint and return Json-response as an Array
86
     *
87
     * @param string $type
88
     *
89
     * @return array
90
     * @throws DeepLException
91
     */
92 4
    public function languages($type = null)
93
    {
94 4
        $url       = $this->buildBaseUrl(self::API_URL_RESOURCE_LANGUAGES);
95 4
        $body      = $this->buildQuery(array('type' => $type));
96 4
        $languages = $this->request($url, $body);
97
98 3
        return $languages;
99
    }
100
101
    /**
102
     * Translate the text string or array from source to destination language
103
     * For detailed info on Parameters see README.md
104
     *
105
     * @param string|string[] $text
106
     * @param string          $sourceLang
107
     * @param string          $targetLang
108
     * @param string          $tagHandling
109
     * @param array|null      $ignoreTags
110
     * @param string          $formality
111
     * @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...
112
     * @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...
113
     * @param array|null      $nonSplittingTags
114
     * @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...
115
     * @param array|null      $splittingTags
116
     *
117
     * @return array
118
     * @throws DeepLException
119
     *
120
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
121
     */
122 12
    public function translate(
123
        $text,
124
        $sourceLang = 'de',
125
        $targetLang = 'en',
126
        $tagHandling = null,
127
        array $ignoreTags = null,
128
        $formality = 'default',
129
        $splitSentences = null,
130
        $preserveFormatting = null,
131
        array $nonSplittingTags = null,
132
        $outlineDetection = null,
133
        array $splittingTags = null
134
    ) {
135 12
        if (is_array($tagHandling)) {
0 ignored issues
show
introduced by
The condition is_array($tagHandling) is always false.
Loading history...
136 1
            throw new \InvalidArgumentException('$tagHandling must be of type String in V2 of DeepLLibrary');
137
        }
138
        $paramsArray = array(
139 11
            'text'                => $text,
140 11
            'source_lang'         => $sourceLang,
141 11
            'target_lang'         => $targetLang,
142 11
            'splitting_tags'      => $splittingTags,
143 11
            'non_splitting_tags'  => $nonSplittingTags,
144 11
            'ignore_tags'         => $ignoreTags,
145 11
            'tag_handling'        => $tagHandling,
146 11
            'formality'           => $formality,
147 11
            'split_sentences'     => $splitSentences,
148 11
            'preserve_formatting' => $preserveFormatting,
149 11
            'outline_detection'   => $outlineDetection,
150
        );
151
152 11
        $paramsArray = $this->removeEmptyParams($paramsArray);
153 11
        $url         = $this->buildBaseUrl();
154 11
        $body        = $this->buildQuery($paramsArray);
155
156
        // request the DeepL API
157 11
        $translationsArray = $this->request($url, $body);
158
159 7
        return $translationsArray['translations'];
160
    }
161
162
    /**
163
     * Calls the usage-Endpoint and return Json-response as an array
164
     *
165
     * @return array
166
     * @throws DeepLException
167
     */
168 1
    public function usage()
169
    {
170 1
        $url   = $this->buildBaseUrl(self::API_URL_RESOURCE_USAGE);
171 1
        $usage = $this->request($url);
172
173 1
        return $usage;
174
    }
175
176
177
    /**
178
     * Creates the Base-Url which all of the 3 API-resources have in common.
179
     *
180
     * @param string $resource
181
     *
182
     * @return string
183
     */
184 18
    protected function buildBaseUrl($resource = 'translate')
185
    {
186 18
        $url = sprintf(
187 18
            self::API_URL_BASE,
188 18
            self::API_URL_SCHEMA,
189 18
            $this->host,
190 18
            $this->apiVersion,
191 18
            $resource,
192 18
            $this->authKey
193
        );
194
195 18
        return $url;
196
    }
197
198
    /**
199
     * @param array $paramsArray
200
     *
201
     * @return string
202
     */
203 21
    protected function buildQuery($paramsArray)
204
    {
205 21
        if (isset($paramsArray['text']) && true === is_array($paramsArray['text'])) {
206 2
            $text = $paramsArray['text'];
207 2
            unset($paramsArray['text']);
208 2
            $textString = '';
209 2
            foreach ($text as $textElement) {
210 2
                $textString .= '&text='.rawurlencode($textElement);
211
            }
212
        }
213
214 21
        foreach ($paramsArray as $key => $value) {
215 21
            if (true === is_array($value)) {
216 7
                $paramsArray[$key] = implode(',', $value);
217
            }
218
        }
219
220 21
        $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

220
        $body = http_build_query($paramsArray, /** @scrutinizer ignore-type */ null, '&');
Loading history...
221
222 21
        if (isset($textString)) {
223 2
            $body = $textString.'&'.$body;
224
        }
225
226 21
        return $body;
227
    }
228
229
230
231
232
    /**
233
     * Make a request to the given URL
234
     *
235
     * @param string $url
236
     * @param string $body
237
     *
238
     * @return array
239
     *
240
     * @throws DeepLException
241
     */
242 16
    protected function request($url, $body = '')
243
    {
244 16
        curl_setopt($this->curl, CURLOPT_POST, true);
245 16
        curl_setopt($this->curl, CURLOPT_URL, $url);
246 16
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
247 16
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
248
249 16
        $response = curl_exec($this->curl);
250
251 16
        if (curl_errno($this->curl)) {
252
            throw new DeepLException('There was a cURL Request Error : ' . curl_error($this->curl));
253
        }
254 16
        $httpCode      = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
255 16
        $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

255
        $responseArray = json_decode(/** @scrutinizer ignore-type */ $response, true);
Loading history...
256
257 16
        if ($httpCode != 200 && is_array($responseArray) && array_key_exists('message', $responseArray)) {
258 3
            throw new DeepLException($responseArray['message'], $httpCode);
259
        }
260
261 13
        if (false === is_array($responseArray)) {
262 2
            throw new DeepLException('The Response seems to not be valid JSON.', $httpCode);
263
        }
264
265 11
        return $responseArray;
266
    }
267
268
    /**
269
     * @param array $paramsArray
270
     *
271
     * @return array
272
     */
273 16
    private function removeEmptyParams($paramsArray)
274
    {
275
276 16
        foreach ($paramsArray as $key => $value) {
277 16
            if (true === empty($value)) {
278 16
                unset($paramsArray[$key]);
279
            }
280
            // Special Workaround for outline_detection which will be unset above
281
            // DeepL assumes outline_detection=1 if it is not send
282
            // in order to deactivate it, we need to send outline_detection=0 to the api
283 16
            if ('outline_detection' === $key) {
284 16
                if (1 === $value) {
285 2
                    unset($paramsArray[$key]);
286
                }
287
288 16
                if (0 === $value) {
289 3
                    $paramsArray[$key] = 0;
290
                }
291
            }
292
        }
293
294 16
        return $paramsArray;
295
    }
296
}
297