Completed
Push — master ( 02d4e1...90d806 )
by Aleksandr
01:56
created

Client   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 333
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 61
lcom 1
cbo 3
dl 0
loc 333
rs 3.52
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A init() 0 7 2
A getError() 0 11 3
A validate() 0 7 2
A filter() 0 10 3
A filterParam() 0 4 1
A validateParam() 0 4 1
A setGuzzleOptions() 0 4 1
A customGuzzleOptions() 0 4 1
B merge() 0 23 8
A buildUrl() 0 8 4
A getGuzzle() 0 4 1
A getContent() 0 20 5
A unSerializeJson() 0 4 1
A unSerializeXml() 0 4 1
A unSerialize() 0 14 4
A addError() 0 4 1
A beforePrepareData() 0 4 1
B prepareData() 0 39 10
B guzzleOptions() 0 28 9

How to fix   Complexity   

Complex Class

Complex classes like Client often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Client, and based on these observations, apply Extract Interface, too.

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
    const TYPE_MULTIPART = 'multipart';
28
29
    protected $protocol = 'https';
30
    protected $url = '';
31
    protected $type = 'json';
32
    protected $output_type;
33
    protected $_guzzleOptions = [];
34
    protected $_custom_guzzle_options = [];
35
    protected $_guzzle;
36
    protected $_errors;
37
38
    /**
39
     * Client constructor.
40
     *
41
     * @param array $config
42
     */
43
    public function __construct(array $config = [])
44
    {
45
        $this->_guzzle = new GuzzleClient();
46
        foreach ($config as $prop => $value) {
47
            $this->$prop = $value;
48
        }
49
        $this->init();
50
    }
51
52
    public function init()
53
    {
54
        $this->method = strtoupper($this->method);
55
        if (!in_array($this->method, ['GET', 'POST'])) {
56
            $this->method = 'GET';
57
        }
58
    }
59
60
    /**
61
     * @param bool $asString
62
     * @return string
63
     */
64
    public function getError($asString = true)
65
    {
66
        if (!$asString) {
67
            return $this->_errors;
68
        }
69
        $error = ['Ошибка валидации параметров'];
70
        foreach ($this->_errors as $param => $errors) {
71
            $error[] = $param . ' - ' . join('; ', $errors);
72
        }
73
        return join("\n", $error);
74
    }
75
76
    public function validate(array $data)
77
    {
78
        foreach ($data as $param => $value) {
79
            $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...
80
        }
81
        return !count($this->_errors);
82
    }
83
84
    /**
85
     * @param array $data
86
     * @return array
87
     */
88
    public function filter(array $data)
89
    {
90
        $result = [];
91
        foreach ($data as $param => $value) {
92
            if (!is_null($filtered = $this->filterParam($param, $value))) {
93
                $result[$param] = $filtered;
94
            }
95
        }
96
        return $result;
97
    }
98
99
    /**
100
     * @param $param
101
     * @param $value
102
     * @return mixed
103
     */
104
    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...
105
    {
106
        return $value;
107
    }
108
109
    /**
110
     * @param $param
111
     * @param $value
112
     * @return bool
113
     */
114
    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...
115
    {
116
        return true;
117
    }
118
119
    /**
120
     * @param $array
121
     */
122
    public function setGuzzleOptions($array)
123
    {
124
        $this->_custom_guzzle_options = $array;
125
    }
126
127
    /**
128
     * @return array
129
     */
130
    protected function customGuzzleOptions()
131
    {
132
        return [];
133
    }
134
135
    /**
136
     * @param $a
137
     * @param $b
138
     * @return array|mixed
139
     */
140
    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...
141
    {
142
        $args = func_get_args();
143
        $res = array_shift($args);
144
        while (!empty($args)) {
145
            $next = array_shift($args);
146
            foreach ($next as $k => $v) {
147
                if (is_int($k)) {
148
                    if (array_key_exists($k, $res)) {
149
                        $res[] = $v;
150
                    } else {
151
                        $res[$k] = $v;
152
                    }
153
                } elseif (\is_array($v) && isset($res[$k]) && \is_array($res[$k])) {
154
                    $res[$k] = self::merge($res[$k], $v);
155
                } else {
156
                    $res[$k] = $v;
157
                }
158
            }
159
        }
160
161
        return $res;
162
    }
163
164
    /**
165
     * @param $url
166
     * @return string
167
     */
168
    protected function buildUrl($url)
169
    {
170
        if (strpos($this->url, '://')) {
171
            return $this->url . ($url ? '/' . $url : '');
172
        }
173
174
        return $this->protocol . '://' . $this->url . ($url ? '/' . $url : '');
175
    }
176
177
    /**
178
     * @return GuzzleClient
179
     */
180
    public function getGuzzle()
181
    {
182
        return $this->_guzzle;
183
    }
184
185
    /**
186
     * @param $urlRequest
187
     * @param array $data
188
     * @return string|\stdClass|\SimpleXMLElement
189
     */
190
    public function getContent($urlRequest, $data = [])
191
    {
192
        $options = [];
193
        $this->guzzleOptions();
194
        $url = $this->buildUrl($urlRequest);
195
        $client = $this->getGuzzle();
196
        $data = $this->prepareData($data);
197
        if ($this->method == 'GET') {
198
            $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 196 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...
199
        } elseif ($this->postDataInBody) {
200
            $options = ['body' => $data];
201
        } elseif ($this->type === self::TYPE_MULTIPART) {
202
            $options = ['multipart' => $data];
203
        } else {
204
            $options = ['form_params' => $data];
205
        }
206
        $request = $client->request($this->method, $url, self::merge($options, $this->_guzzleOptions));
207
        $this->request = $request;
208
        return $this->unSerialize($request->getBody()->getContents());
209
    }
210
211
    /**
212
     * @param $data
213
     * @return mixed
214
     */
215
    protected function unSerializeJson($data)
216
    {
217
        return \GuzzleHttp\json_decode($data);
218
    }
219
220
    /**
221
     * @param $data
222
     * @return \SimpleXMLElement
223
     */
224
    protected function unSerializeXml($data)
225
    {
226
        return simplexml_load_string($data);
227
    }
228
229
    /**
230
     * @param $data
231
     * @return mixed|null|\SimpleXMLElement
232
     */
233
    public function unSerialize($data)
234
    {
235
        $type = $this->output_type ? $this->output_type : $this->type;
236
237
        switch ($type) {
238
            case self::TYPE_JSON:
239
                return $this->unSerializeJson($data);
240
                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...
241
            case self::TYPE_XML:
242
                return $this->unSerializeXml($data);
243
                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...
244
        }
245
        return $data;
246
    }
247
248
    /**
249
     * @param $param
250
     * @param $message
251
     */
252
    public function addError($param, $message)
253
    {
254
        $this->_errors[$param][] = $message;
255
    }
256
257
    /**
258
     * @param array $data
259
     * @return array
260
     */
261
    protected function beforePrepareData(array $data)
262
    {
263
        return $data;
264
    }
265
266
    /**
267
     * @param $data
268
     * @return string|array
269
     * @throws \Exception
270
     */
271
    protected function prepareData(array $data)
272
    {
273
        $data = $this->beforePrepareData($data);
274
        $data = $this->filter($data);
275
276
        if (!$this->validate($data)) {
277
            throw new \Exception($this->getError());
278
        }
279
        if ($this->method === 'GET') {
280
            return $data;
281
        }
282
        switch ($this->type) {
283
            case self::TYPE_JSON:
284
                $data = \GuzzleHttp\json_encode($data);
285
                break;
286
            case self::TYPE_XML:
287
                throw new \Exception('Xml type is not implemented yet');
288
                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...
289
            case self::TYPE_FORM:
290
                break;
291
            case self::TYPE_MULTIPART:
292
                $prepared = [];
293
                foreach ($data as $param => $value) {
294
                    if (\is_array($value)) {
295
                        foreach ($value as $item) {
296
                            $prepared[] = ['name' => $param . '[]', 'contents' => $item];
297
                        }
298
                    } else {
299
                        $prepared[] = ['name' => $param, 'contents' => $value];
300
                    }
301
                }
302
                $data = $prepared;
303
                break;
304
            default:
305
                throw new \Exception('Type is not supported');
306
                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...
307
        }
308
        return $data;
309
    }
310
311
    /**
312
     * @throws \Exception
313
     */
314
    public function guzzleOptions()
315
    {
316
        $options = [
317
            'headers' => []
318
        ];
319
        if ($this->proxy) {
320
            $options['proxy'] = $this->proxy;
321
        }
322
        switch ($this->type) {
323
            case self::TYPE_JSON:
324
                $options['headers']['content-type'] = 'application/json';
325
                break;
326
            case self::TYPE_XML:
327
                $options['headers']['content-type'] = 'application/xml';
328
                break;
329
            case self::TYPE_FORM:
330
                $options['headers']['content-type'] = 'application/x-www-form-urlencoded';
331
                break;
332
            case self::TYPE_MULTIPART:
333
                break;
334
            default:
335
                throw new \Exception('Type is not supported');
336
        }
337
        if (($this->login || $this->password) && $this->useAuth) {
338
            $options['auth'] = [$this->login, $this->password];
339
        }
340
        $this->_guzzleOptions = self::merge($options, $this->_custom_guzzle_options, $this->customGuzzleOptions());
0 ignored issues
show
Documentation Bug introduced by
It seems like self::merge($options, $t...>customGuzzleOptions()) of type * is incompatible with the declared type array of property $_guzzleOptions.

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...
341
    }
342
}