Completed
Push — master ( 6b3dc5...41c060 )
by Klochok
09:45
created

Connection::decodeErrorBody()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 5
Bugs 1 Features 1
Metric Value
c 5
b 1
f 1
dl 0
loc 14
ccs 0
cts 9
cp 0
rs 9.4285
cc 3
eloc 9
nc 4
nop 1
crap 12
1
<?php
2
3
/*
4
 * Tools to use API as ActiveRecord for Yii2
5
 *
6
 * @link      https://github.com/hiqdev/yii2-hiart
7
 * @package   yii2-hiart
8
 * @license   BSD-3-Clause
9
 * @copyright Copyright (c) 2015-2016, HiQDev (http://hiqdev.com/)
10
 */
11
12
namespace hiqdev\hiart;
13
14
use Closure;
15
use GuzzleHttp\Client as Handler;
16
use Yii;
17
use yii\base\Component;
18
use yii\base\InvalidConfigException;
19
use yii\base\InvalidParamException;
20
use yii\helpers\Json;
21
22
/**
23
 * Connection class.
24
 *
25
 * Example configuration:
26
 * ```php
27
 * 'components' => [
28
 *     'hiart' => [
29
 *         'class' => 'hiqdev\hiart\Connection',
30
 *         'config' => [
31
 *             'base_uri' => 'https://api.site.com/',
32
 *         ],
33
 *     ],
34
 * ],
35
 * ```
36
 */
37
class Connection extends Component
38
{
39
    const EVENT_AFTER_OPEN = 'afterOpen';
40
41
    /**
42
     * @var array Config
43
     */
44
    public $config = [];
45
46
    /**
47
     * @var Handler
48
     */
49
    protected static $_handler = null;
50
51
    /**
52
     * @var array authorization config
53
     */
54
    protected $_auth = [];
55
56
    /**
57
     * @var bool is auth disabled
58
     */
59
    protected $_disabledAuth = false;
60
61
    /**
62
     * @var Closure Callback to test if API response has error
63
     * The function signature: `function ($response)`
64
     * Must return `null`, if the response does not contain an error.
65
     */
66
    protected $_errorChecker;
67
68
    public function setAuth($auth)
69
    {
70
        $this->_auth = $auth;
71
    }
72
73
    /**
74
     * Returns auth settings.
75
     * @return array
76
     */
77 2
    public function getAuth()
78
    {
79 2
        if ($this->_disabledAuth) {
80
            return [];
81
        }
82 2
        if ($this->_auth instanceof Closure) {
83
            $this->_auth = call_user_func($this->_auth, $this);
0 ignored issues
show
Documentation Bug introduced by
It seems like call_user_func($this->_auth, $this) of type * is incompatible with the declared type array of property $_auth.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
84
        }
85
86 2
        return $this->_auth;
87
    }
88
89
    public function disableAuth()
90
    {
91
        $this->_disabledAuth = true;
92
    }
93
94
    public function enableAuth()
95
    {
96
        $this->_disabledAuth = false;
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     * @throws InvalidConfigException
102
     */
103 2
    public function init()
104
    {
105 2
        if (!$this->config['base_uri']) {
106
            throw new InvalidConfigException('The `base_uri` config option must be set');
107
        }
108
109 2
        if (!isset($this->config['headers']['User-Agent'])) {
110 2
            $this->config['headers']['User-Agent'] = 'HiArt/0.x';
111 2
        }
112 2
    }
113
114
    /**
115
     * Closes the connection when this component is being serialized.
116
     * @return array
117
     */
118
    public function __sleep()
119
    {
120
        return array_keys(get_object_vars($this));
121
    }
122
123
    /**
124
     * Returns the name of the DB driver for the current [[dsn]].
125
     *
126
     * @return string name of the DB driver
127
     */
128
    public function getDriverName()
129
    {
130
        return 'hiart';
131
    }
132
133
    /**
134
     * Creates a command for execution.
135
     *
136
     * @param array $config the configuration for the Command class
137
     *
138
     * @return Command the DB command
139
     */
140
    public function createCommand($config = [])
141
    {
142
        $config['db'] = $this;
143
        $command      = new Command($config);
144
145
        return $command;
146
    }
147
148
    /**
149
     * Creates new query builder instance.
150
     *
151
     * @return QueryBuilder
152
     */
153
    public function getQueryBuilder()
154
    {
155
        return new QueryBuilder($this);
156
    }
157
158
    /**
159
     * Performs GET HTTP request.
160
     * @param string $url   URL
161
     * @param array  $query query options
162
     * @param string $body  request body
163
     * @param bool   $raw   if response body contains JSON and should be decoded
164
     * @throws HiArtException
165
     * @throws \yii\base\InvalidConfigException
166
     * @return mixed response
167
     */
168 2
    public function get($url, $query = [], $body = null, $raw = false)
169
    {
170 2
        return $this->makeRequest('GET', $url, $query, $body, $raw);
171
    }
172
173
    /**
174
     * Performs HEAD HTTP request.
175
     * @param string $url   URL
176
     * @param array  $query query options
177
     * @param string $body  request body
178
     * @throws HiArtException
179
     * @throws \yii\base\InvalidConfigException
180
     * @return mixed response
181
     */
182
    public function head($url, $query = [], $body = null)
183
    {
184
        return $this->makeRequest('HEAD', $url, $query, $body, $raw);
0 ignored issues
show
Bug introduced by
The variable $raw does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
185
    }
186
187
    /**
188
     * Performs POST HTTP request.
189
     * @param string $url   URL
190
     * @param array  $query query options
191
     * @param string $body  request body
192
     * @param bool   $raw   if response body contains JSON and should be decoded
193
     * @throws HiArtException
194
     * @throws \yii\base\InvalidConfigException
195
     * @return mixed response
196
     */
197
    public function post($url, $query = [], $body = null, $raw = false)
198
    {
199
        return $this->makeRequest('POST', $url, $query, $body, $raw);
200
    }
201
202
    /**
203
     * Performs PUT HTTP request.
204
     * @param string $url   URL
205
     * @param array  $query query options
206
     * @param string $body  request body
207
     * @param bool   $raw   if response body contains JSON and should be decoded
208
     * @throws HiArtException
209
     * @throws \yii\base\InvalidConfigException
210
     * @return mixed response
211
     */
212
    public function put($url, $query = [], $body = null, $raw = false)
213
    {
214
        return $this->makeRequest('PUT', $url, $query, $body, $raw);
215
    }
216
217
    /**
218
     * Performs DELETE HTTP request.
219
     * @param string $url   URL
220
     * @param array  $query query options
221
     * @param string $body  request body
222
     * @param bool   $raw   if response body contains JSON and should be decoded
223
     * @throws HiArtException
224
     * @throws \yii\base\InvalidConfigException
225
     * @return mixed response
226
     */
227
    public function delete($url, $query = [], $body = null, $raw = false)
228
    {
229
        return $this->makeRequest('DELETE', $url, $query, $body, $raw);
230
    }
231
232
    /**
233
     * XXX DEPRECATED in favour of post().
234
     * @param $url
235
     * @param array $query
0 ignored issues
show
Bug introduced by
There is no parameter named $query. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
236
     * @return mixed
237
     */
238
    public function perform($url, $body = [])
239
    {
240
        return $this->makeRequest('DELETE', $url, [], $body);
0 ignored issues
show
Documentation introduced by
$body is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
241
    }
242
243
    /**
244
     * Make request and check for error.
245
     * @param string $url   URL
246
     * @param array  $query query options, (GET parameters)
247
     * @param string $body  request body, (POST parameters)
248
     * @param bool   $raw   if response body contains JSON and should be decoded
249
     * @throws HiArtException
250
     * @throws \yii\base\InvalidConfigException
251
     * @return mixed response
252
     */
253 2
    public function makeRequest($method, $url, $query = [], $body = null, $raw = false)
254
    {
255 2
        $result = $this->handleRequest($method, $this->prepareUrl($url, $query), $body, $raw);
0 ignored issues
show
Documentation introduced by
$this->prepareUrl($url, $query) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
256
257 2
        return $this->checkResponse($result, $url, $query);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->handleRequest($me..., $query), $body, $raw) on line 255 can also be of type string; however, hiqdev\hiart\Connection::checkResponse() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
258
    }
259
260
    /**
261
     * Creates URL.
262
     * @param mixed $path path
263
     * @param array $query query options
264
     * @return array
265
     */
266 2
    private function prepareUrl($path, array $query = [])
267
    {
268 2
        $url = $path;
269 2
        $query = array_merge($this->getAuth(), $query);
270 2
        if (!empty($query)) {
271
            $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($query);
272
        }
273
274 2
        return $url;
275
    }
276
277
    /**
278
     * Handles the request with handler.
279
     * Returns array or raw response content, if $raw is true.
280
     *
281
     * @param string $method POST, GET, etc
282
     * @param string $url the URL for request, not including proto and site
283
     * @param array|string $body the request body. When array - will be sent as POST params, otherwise - as RAW body.
284
     * @param bool $raw Whether to decode data, when response is decodeable (JSON).
285
     * @return array|string
286
     */
287 2
    protected function handleRequest($method, $url, $body = null, $raw = false)
288
    {
289 2
        $method  = strtoupper($method);
290 2
        $profile = $method . ' ' . $url . '#' . (is_array($body) ? http_build_query($body) : $body);
291 2
        $options = [(is_array($body) ? 'form_params' : 'body') => $body];
292 2
        Yii::beginProfile($profile, __METHOD__);
293 2
        $response = $this->getHandler()->request($method, $url, $options);
294 2
        Yii::endProfile($profile, __METHOD__);
295
296 2
        $res = $response->getBody()->getContents();
297 2
        if (!$raw && preg_grep('|application/json|i', $response->getHeader('Content-Type'))) {
298
            $res = Json::decode($res);
299
        }
300
301 2
        return $res;
302
    }
303
304
    /**
305
     * Returns the request handler (Guzzle client for the moment).
306
     * Creates and setups handler if not set.
307
     * @return Handler
308
     */
309 2
    public function getHandler()
310
    {
311 2
        if (static::$_handler === null) {
312
            static::$_handler = new Handler($this->config);
313
        }
314
315 2
        return static::$_handler;
316
    }
317
318
    /**
319
     * Set handler manually.
320
     * @param Handler $value
321
     * @return void
322
     */
323 2
    public function setHandler($value)
324
    {
325 2
        static::$_handler = $value;
326 2
    }
327
328
    /**
329
     * @return boolean
330
     */
331
    public function isDisabledAuth()
332
    {
333
        return $this->_disabledAuth;
334
    }
335
336
    /**
337
     * @param boolean $disabledAuth
338
     */
339
    public function setDisabledAuth($disabledAuth)
340
    {
341
        $this->_disabledAuth = $disabledAuth;
342
    }
343
344
    /**
345
     * Try to decode error information if it is valid json, return it if not.
346
     * @param $body
347
     * @return mixed
348
     */
349
    protected function decodeErrorBody($body)
350
    {
351
        try {
352
            $decoded = Json::decode($body);
353
            if (isset($decoded['error'])) {
354
                $decoded['error'] = preg_replace('/\b\w+?Exception\[/',
355
                    "<span style=\"color: red;\">\\0</span>\n               ", $decoded['error']);
356
            }
357
358
            return $decoded;
359
        } catch (InvalidParamException $e) {
360
            return $body;
361
        }
362
    }
363
364
    /**
365
     * Setter for errorChecker.
366
     * @param Closure|array $value
367
     * @return void
368
     */
369 2
    public function setErrorChecker($value)
370
    {
371 2
        $this->_errorChecker = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value can also be of type array. However, the property $_errorChecker is declared as type object<Closure>. 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...
372 2
    }
373
374
    /**
375
     * Checks response with checkError method and raises exception if error.
376
     * @param array  $response response data from API
377
     * @param string $url      request URL
378
     * @param array  $options  request data
379
     * @throws ErrorResponseException
380
     * @return array
381
     */
382 2
    protected function checkResponse($response, $url, $options)
383
    {
384 2
        $error = $this->checkError($response);
385 2
        if (isset($error)) {
386 1
            throw new ErrorResponseException($error, [
387 1
                'requestUrl' => $url,
388 1
                'request'    => $options,
389 1
                'response'   => $response,
390 1
            ]);
391
        }
392
393 1
        return $response;
394
    }
395
396
    /**
397
     * Checks response with errorChecker callback and returns not null if error.
398
     * @param array  $response response data from API
399
     * @return null|string
400
     */
401 2
    public function checkError($response)
402
    {
403 2
        if (isset($this->_errorChecker)) {
404 2
            return call_user_func($this->_errorChecker, $response);
405
        }
406
407
        return null;
408
    }
409
}
410