Completed
Push — master ( ebbdca...b75495 )
by Dmitry
02:52
created

Connection::post()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 6
Bugs 1 Features 1
Metric Value
c 6
b 1
f 1
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 4
crap 2
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 Closure Callback to test if API response has error
58
     * The function signature: `function ($response)`
59
     * Must return `null`, if the response does not contain an error.
60
     */
61
    protected $_errorChecker;
62
63
    public function setAuth($auth)
64
    {
65
        $this->_auth = $auth;
66
    }
67
68 2
    public function getAuth()
69
    {
70 2
        if ($this->_auth instanceof Closure) {
71
            $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...
72
        }
73
74 2
        return $this->_auth;
75 2
    }
76
77
    /**
78
     * {@inheritdoc}
79
     * @throws InvalidConfigException
80
     */
81 2
    public function init()
82
    {
83 2
        if (!$this->config['base_uri']) {
84
            throw new InvalidConfigException('The `base_uri` config option must be set');
85
        }
86 2
    }
87
88
    /**
89
     * Closes the connection when this component is being serialized.
90
     * @return array
91
     */
92
    public function __sleep()
93
    {
94
        return array_keys(get_object_vars($this));
95
    }
96
97
    /**
98
     * Returns the name of the DB driver for the current [[dsn]].
99
     *
100
     * @return string name of the DB driver
101
     */
102
    public function getDriverName()
103
    {
104
        return 'hiresource';
105
    }
106
107
    /**
108
     * Creates a command for execution.
109
     *
110
     * @param array $config the configuration for the Command class
111
     *
112
     * @return Command the DB command
113
     */
114
    public function createCommand($config = [])
115
    {
116
        $config['db'] = $this;
117
        $command      = new Command($config);
118
119
        return $command;
120
    }
121
122
    /**
123
     * Creates new query builder instance.
124
     *
125
     * @return QueryBuilder
126
     */
127
    public function getQueryBuilder()
128
    {
129
        return new QueryBuilder($this);
130
    }
131
132
    /**
133
     * Performs GET HTTP request.
134
     * @param string $url   URL
135
     * @param array  $query query options
136
     * @param string $body  request body
137
     * @param bool   $raw   if response body contains JSON and should be decoded
138
     * @throws HiArtException
139
     * @throws \yii\base\InvalidConfigException
140
     * @return mixed response
141
     */
142 1
    public function get($url, $query = [], $body = null, $raw = false)
143
    {
144 1
        return $this->makeRequest('GET', $url, $query, $body, $raw);
145
    }
146
147
    /**
148
     * Performs HEAD HTTP request.
149
     * @param string $url   URL
150
     * @param array  $query query options
151
     * @param string $body  request body
152
     * @throws HiArtException
153
     * @throws \yii\base\InvalidConfigException
154
     * @return mixed response
155
     */
156
    public function head($url, $query = [], $body = null)
157
    {
158
        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...
159
    }
160
161
    /**
162
     * Performs POST HTTP request.
163
     * @param string $url   URL
164
     * @param array  $query query options
165
     * @param string $body  request body
166
     * @param bool   $raw   if response body contains JSON and should be decoded
167
     * @throws HiArtException
168
     * @throws \yii\base\InvalidConfigException
169
     * @return mixed response
170
     */
171
    public function post($url, $query = [], $body = null, $raw = false)
172
    {
173
        return $this->makeRequest('POST', $url, $query, $body, $raw);
174
    }
175
176
    /**
177
     * Performs PUT HTTP request.
178
     * @param string $url   URL
179
     * @param array  $query query options
180
     * @param string $body  request body
181
     * @param bool   $raw   if response body contains JSON and should be decoded
182
     * @throws HiArtException
183
     * @throws \yii\base\InvalidConfigException
184
     * @return mixed response
185
     */
186
    public function put($url, $query = [], $body = null, $raw = false)
187
    {
188
        return $this->makeRequest('PUT', $url, $query, $body, $raw);
189
    }
190
191
    /**
192
     * Performs DELETE HTTP request.
193
     * @param string $url   URL
194
     * @param array  $query query options
195
     * @param string $body  request body
196
     * @param bool   $raw   if response body contains JSON and should be decoded
197
     * @throws HiArtException
198
     * @throws \yii\base\InvalidConfigException
199
     * @return mixed response
200
     */
201
    public function delete($url, $query = [], $body = null, $raw = false)
202
    {
203
        return $this->makeRequest('DELETE', $url, $query, $body, $raw);
204
    }
205
206
    /**
207
     * XXX DEPRECATED in favour of post().
208
     * @param $url
209
     * @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...
210
     * @return mixed
211
     */
212
    public function perform($url, $body = [])
213
    {
214
        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...
215
    }
216
217
    /**
218
     * Make request and check for error.
219
     * @param string $url   URL
220
     * @param array  $query query options, (GET parameters)
221
     * @param string $body  request body, (POST parameters)
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 1
    public function makeRequest($method, $url, $query = [], $body = null, $raw = false)
228
    {
229
        $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...
230
231 1
        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 229 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...
232
    }
233
234
    /**
235
     * Creates URL.
236
     * @param mixed $path path
237
     * @param array $query query options
238
     * @return array
239
     */
240 2
    private function prepareUrl($path, array $query = [])
241
    {
242 2
        $url = $path;
243
        $query = array_merge($this->getAuth(), $query);
244 2
        if (!empty($query)) {
245
            $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($query);
246
        }
247
248 2
        return $url;
249 2
    }
250
251
    /**
252
     * Handles the request with handler.
253
     * Returns array or raw response content, if $raw is true.
254
     *
255
     * @param string $method POST, GET, etc
256
     * @param string $url the URL for request, not including proto and site
257
     * @param array|string $body the request body. When array - will be sent as POST params, otherwise - as RAW body.
258
     * @param bool $raw Whether to decode data, when response is decodeable (JSON).
259
     * @return array|string
260
     */
261 2
    protected function handleRequest($method, $url, $body = null, $raw = false)
262
    {
263
        $method  = strtoupper($method);
264
        $profile = $method . ' ' . $url . '#' . (is_array($body) ? http_build_query($body) : $body);
265
        $options = [(is_array($body) ? 'form_params' : 'body') => $body];
266
        Yii::beginProfile($profile, __METHOD__);
267
        $response = $this->getHandler()->request($method, $url, $options);
268
        Yii::endProfile($profile, __METHOD__);
269
270
        $res = $response->getBody()->getContents();
271
        if (!$raw && preg_grep('|application/json|i', $response->getHeader('Content-Type'))) {
272
            $res = Json::decode($res);
273
        }
274
275 2
        return $res;
276
    }
277
278
    /**
279
     * Returns the request handler (Guzzle client for the moment).
280
     * Creates and setups handler if not set.
281
     * @return Handler
282
     */
283 2
    public function getHandler()
284
    {
285 2
        if (static::$_handler === null) {
286
            static::$_handler = new Handler($this->config);
287
            static::$_handler->setUserAgent('hiart/0.x');
288
        }
289
290 2
        return static::$_handler;
291
    }
292
293
    /**
294
     * Set handler manually.
295
     * @param Handler $value
296
     * @return void
297
     */
298 2
    public function setHandler($value)
299
    {
300 2
        static::$_handler = $value;
301 2
    }
302
303
    /**
304
     * Try to decode error information if it is valid json, return it if not.
305
     * @param $body
306
     * @return mixed
307
     */
308
    protected function decodeErrorBody($body)
309
    {
310
        try {
311
            $decoded = Json::decode($body);
312
            if (isset($decoded['error'])) {
313
                $decoded['error'] = preg_replace('/\b\w+?Exception\[/',
314
                    "<span style=\"color: red;\">\\0</span>\n               ", $decoded['error']);
315
            }
316
317
            return $decoded;
318
        } catch (InvalidParamException $e) {
319
            return $body;
320
        }
321
    }
322
323
    /**
324
     * Setter for errorChecker.
325
     * @param Closure|array $value
326
     * @return void
327
     */
328 1
    public function setErrorChecker($value)
329
    {
330 1
        $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...
331 1
    }
332
333
    /**
334
     * Checks response with errorChecker callback and raises exception if error.
335
     * @param array  $response response data from API
336
     * @param string $url      request URL
337
     * @param array  $options  request data
338
     * @throws ErrorResponseException
339
     * @return array
340
     */
341 2
    protected function checkResponse($response, $url, $options)
342
    {
343 2
        if (isset($this->_errorChecker)) {
344
            $error = call_user_func($this->_errorChecker, $response);
345 2
            if ($error !== null) {
346
                throw new ErrorResponseException($error, [
347
                    'requestUrl' => $url,
348
                    'request'    => $options,
349
                    'response'   => $response,
350
                ]);
351
            }
352
        }
353
354 1
        return $response;
355
    }
356
}
357