Completed
Push — master ( 3c4e89...4169f1 )
by Aleksandr
02:03
created

Client::merge()   C

Complexity

Conditions 8
Paths 6

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 6.1403
c 0
b 0
f 0
cc 8
eloc 16
nc 6
nop 2
1
<?php
2
3
namespace carono\rest;
4
5
use function GuzzleHttp\Psr7\build_query;
6
use GuzzleHttp\Client as GuzzleClient;
7
use Psr\Http\Message\ResponseInterface;
8
use Psr\Http\Message\StreamInterface;
9
10
class Client
11
{
12
    public $login;
13
    public $password;
14
    public $proxy;
15
    public $method = 'GET';
16
    public $postDataInBody = false;
17
    public $useAuth = true;
18
19
    /**
20
     * @var ResponseInterface
21
     */
22
    public $request;
23
24
    const TYPE_JSON = 'json';
25
    const TYPE_XML = 'xml';
26
    const TYPE_FORM = 'form';
27
28
    protected $protocol = 'https';
29
    protected $url = '';
30
    protected $type = 'json';
31
    protected $output_type;
32
    protected $_guzzleOptions = [];
33
    protected $_custom_guzzle_options = [];
34
    protected $_guzzle;
35
    protected $_errors;
36
37
    /**
38
     * Client constructor.
39
     *
40
     * @param array $config
41
     */
42
    public function __construct(array $config = [])
43
    {
44
        $this->_guzzle = new GuzzleClient();
45
        foreach ($config as $prop => $value) {
46
            $this->$prop = $value;
47
        }
48
        $this->init();
49
    }
50
51
    public function init()
52
    {
53
        $this->method = strtoupper($this->method);
54
        if (!in_array($this->method, ['GET', 'POST'])) {
55
            $this->method = 'GET';
56
        }
57
    }
58
59
    /**
60
     * @param bool $asString
61
     * @return string
62
     */
63
    public function getError($asString = true)
64
    {
65
        if (!$asString) {
66
            return $this->_errors;
67
        }
68
        $error = ['Ошибка валидации параметров'];
69
        foreach ($this->_errors as $param => $errors) {
70
            $error[] = $param . ' - ' . join('; ', $errors);
71
        }
72
        return join("\n", $error);
73
    }
74
75
    public function validate(array $data)
76
    {
77
        foreach ($data as $param => $value) {
78
            $this->validateParam($param, $value);
0 ignored issues
show
Unused Code introduced by
The call to the method carono\rest\Client::validateParam() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
79
        }
80
        return !count($this->_errors);
81
    }
82
83
    /**
84
     * @param array $data
85
     * @return array
86
     */
87
    public function filter(array $data)
88
    {
89
        $result = [];
90
        foreach ($data as $param => $value) {
91
            if (!is_null($filtered = $this->filterParam($param, $value))) {
92
                $result[$param] = $filtered;
93
            }
94
        }
95
        return $result;
96
    }
97
98
    /**
99
     * @param $param
100
     * @param $value
101
     * @return mixed
102
     */
103
    public function filterParam($param, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $param is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
104
    {
105
        return $value;
106
    }
107
108
    /**
109
     * @param $param
110
     * @param $value
111
     * @return bool
112
     */
113
    public function validateParam($param, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $param is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
114
    {
115
        return true;
116
    }
117
118
    public function setGuzzleOptions($array)
119
    {
120
        $this->_custom_guzzle_options = $array;
121
    }
122
123
    /**
124
     * @return array
125
     */
126
    protected function customGuzzleOptions()
127
    {
128
        return [];
129
    }
130
131
    protected static function merge($a, $b)
0 ignored issues
show
Unused Code introduced by
The parameter $a is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $b is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
132
    {
133
        $args = func_get_args();
134
        $res = array_shift($args);
135
        while (!empty($args)) {
136
            $next = array_shift($args);
137
            foreach ($next as $k => $v) {
138
                if (is_int($k)) {
139
                    if (array_key_exists($k, $res)) {
140
                        $res[] = $v;
141
                    } else {
142
                        $res[$k] = $v;
143
                    }
144
                } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
145
                    $res[$k] = self::merge($res[$k], $v);
146
                } else {
147
                    $res[$k] = $v;
148
                }
149
            }
150
        }
151
152
        return $res;
153
    }
154
155
    /**
156
     * @param $url
157
     * @return string
158
     */
159
    protected function buildUrl($url)
160
    {
161
        if (strpos($this->url, '://')) {
162
            return $this->url . ($url ? '/' . $url : '');
163
        } else {
164
            return $this->protocol . '://' . $this->url . ($url ? '/' . $url : '');
165
        }
166
    }
167
168
    /**
169
     * @return GuzzleClient
170
     */
171
    public function getGuzzle()
172
    {
173
        return $this->_guzzle;
174
    }
175
176
    /**
177
     * @param $urlRequest
178
     * @param array $data
179
     * @return string|\stdClass|\SimpleXMLElement
180
     */
181
    public function getContent($urlRequest, $data = [])
182
    {
183
        $options = [];
184
        $this->guzzleOptions();
185
        $url = $this->buildUrl($urlRequest);
186
        $client = $this->getGuzzle();
187
        $data = $this->prepareData($data);
188
        if ($this->method == 'GET') {
189
            $url = $url . (strpos($url, '?') ? '&' : '?') . build_query($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->prepareData($data) on line 187 can also be of type string; however, GuzzleHttp\Psr7\build_query() 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...
190
        } elseif ($this->postDataInBody) {
191
            $options = ['body' => $data];
192
        } else {
193
            $options = ['form_params' => $data];
194
        }
195
        $request = $client->request($this->method, $url, self::merge($options, $this->_guzzleOptions));
196
        $this->request = $request;
197
        return $this->unSerialize($request->getBody()->getContents());
198
    }
199
200
    /**
201
     * @param $data
202
     * @return mixed
203
     */
204
    protected function unSerializeJson($data)
205
    {
206
        return \GuzzleHttp\json_decode($data);
207
    }
208
209
    /**
210
     * @param $data
211
     * @return \SimpleXMLElement
212
     */
213
    protected function unSerializeXml($data)
214
    {
215
        return simplexml_load_string($data);
216
    }
217
218
    /**
219
     * @param $data
220
     * @return mixed|null|\SimpleXMLElement
221
     */
222
    public function unSerialize($data)
223
    {
224
        $type = $this->output_type ? $this->output_type : $this->type;
225
226
        switch ($type) {
227
            case self::TYPE_JSON:
228
                return $this->unSerializeJson($data);
229
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
230
            case self::TYPE_XML:
231
                return $this->unSerializeXml($data);
232
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
233
        }
234
        return $data;
235
    }
236
237
    /**
238
     * @param $param
239
     * @param $message
240
     */
241
    public function addError($param, $message)
242
    {
243
        $this->_errors[$param][] = $message;
244
    }
245
246
    /**
247
     * @param array $data
248
     * @return array
249
     */
250
    protected function beforePrepareData(array $data)
251
    {
252
        return $data;
253
    }
254
255
    /**
256
     * @param $data
257
     * @return string|array
258
     * @throws \Exception
259
     */
260
    protected function prepareData(array $data)
261
    {
262
        $data = $this->beforePrepareData($data);
263
        $data = $this->filter($data);
264
265
        if (!$this->validate($data)) {
266
            throw new \Exception($this->getError());
267
        }
268
        if ($this->method == 'GET') {
269
            return $data;
270
        }
271
        switch ($this->type) {
272
            case self::TYPE_JSON:
273
                $data = \GuzzleHttp\json_encode($data);
274
                break;
275
            case self::TYPE_XML:
276
                throw new \Exception('Xml type is not implemented yet');
277
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
278
            case self::TYPE_FORM:
279
                break;
280
            default:
281
                throw new \Exception('Type is not supported');
282
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
283
        }
284
        return $data;
285
    }
286
287
    /**
288
     * @throws \Exception
289
     */
290
    public function guzzleOptions()
291
    {
292
        $options = [
293
            'headers' => []
294
        ];
295
        if ($this->proxy) {
296
            $options['proxy'] = $this->proxy;
297
        }
298
        switch ($this->type) {
299
            case self::TYPE_JSON:
300
                $options['headers']['content-type'] = 'application/json';
301
                break;
302
            case self::TYPE_XML:
303
                $options['headers']['content-type'] = 'application/xml';
304
                break;
305
            case self::TYPE_FORM:
306
                $options['headers']['content-type'] = 'application/x-www-form-urlencoded';
307
                break;
308
309
            default:
310
                throw new \Exception('Type is not supported');
311
        }
312
        if (($this->login || $this->password) && $this->useAuth) {
313
            $options['auth'] = [$this->login, $this->password];
314
        }
315
        $this->_guzzleOptions = self::merge($options, $this->_custom_guzzle_options, $this->customGuzzleOptions());
316
    }
317
}