Completed
Push — master ( adc836...85a2ca )
by Kevin
03:35
created

AbstractClient::parseResponse()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.552
c 0
b 0
f 0
cc 4
nc 4
nop 1
1
<?php
2
3
namespace PCextreme\Cloudstack;
4
5
use GuzzleHttp\Client as HttpClient;
6
use GuzzleHttp\ClientInterface as HttpClientInterface;
7
use GuzzleHttp\Exception\BadResponseException;
8
use PCextreme\Cloudstack\RequestFactory;
9
use Psr\Http\Message\RequestInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use UnexpectedValueException;
12
13
abstract class AbstractClient
14
{
15
    /**
16
     * @var string
17
     */
18
    const METHOD_GET = 'GET';
19
20
    /**
21
     * @var string
22
     */
23
    const METHOD_POST = 'POST';
24
25
    /**
26
     * @var RequestFactory
27
     */
28
    protected $requestFactory;
29
30
    /**
31
     * @var HttpClientInterface
32
     */
33
    protected $httpClient;
34
35
    /**
36
     * Constructs a new Cloudstack client instance.
37
     *
38
     * @param  array  $options
39
     *     An array of options to set on this client.
40
     * @param  array  $collaborators
41
     *     An array of collaborators that may be used to override
42
     *     this provider's default behavior. Collaborators include
43
     *     `requestFactory` and `httpClient`.
44
     */
45
    public function __construct(array $options = [], array $collaborators = [])
46
    {
47
        if (empty($collaborators['requestFactory'])) {
48
            $collaborators['requestFactory'] = new RequestFactory();
49
        }
50
        $this->setRequestFactory($collaborators['requestFactory']);
51
52
        if (empty($collaborators['httpClient'])) {
53
            $clientOptions = $this->getAllowedClientOptions($options);
54
55
            $collaborators['httpClient'] = new HttpClient(
56
                array_intersect_key($options, array_flip($clientOptions))
57
            );
58
        }
59
        $this->setHttpClient($collaborators['httpClient']);
60
    }
61
62
    /**
63
     * Return the list of options that can be passed to the HttpClient
64
     *
65
     * @param  array  $options
66
     * @return array
67
     */
68
    protected function getAllowedClientOptions(array $options)
69
    {
70
        $clientOptions = ['timeout', 'proxy'];
71
72
        // Only allow turning off ssl verification is it's for a proxy
73
        if (! empty($options['proxy'])) {
74
            $clientOptions[] = 'verify';
75
        }
76
77
        return $clientOptions;
78
    }
79
80
    /**
81
     * Returns a PSR-7 request instance that is not authenticated.
82
     *
83
     * @param  string  $method
84
     * @param  string  $url
85
     * @param  array   $options
86
     * @return RequestInterface
87
     */
88
    public function getRequest($method, $url, array $options = [])
89
    {
90
        return $this->createRequest($method, $url, $options);
91
    }
92
93
    /**
94
     * Creates a PSR-7 request instance.
95
     *
96
     * @param  string  $method
97
     * @param  string  $url
98
     * @param  array   $options
99
     * @return RequestInterface
100
     */
101
    protected function createRequest($method, $url, array $options)
102
    {
103
        $factory = $this->getRequestFactory();
104
105
        return $factory->getRequestWithOptions($method, $url, $options);
106
    }
107
108
    /**
109
     * Sends a request instance and returns a response instance.
110
     *
111
     * @param  RequestInterface  $request
112
     * @return ResponseInterface
113
     */
114
    protected function sendRequest(RequestInterface $request)
115
    {
116
        try {
117
            $response = $this->getHttpClient()->send($request);
118
        } catch (BadResponseException $e) {
119
            $response = $e->getResponse();
120
        }
121
122
        return $response;
123
    }
124
125
    /**
126
     * Sends a request and returns the parsed response.
127
     *
128
     * @param  RequestInterface  $request
129
     * @return mixed
130
     */
131
    public function getResponse(RequestInterface $request)
132
    {
133
        $response = $this->sendRequest($request);
134
        $parsed = $this->parseResponse($response);
0 ignored issues
show
Bug introduced by
It seems like $response defined by $this->sendRequest($request) on line 133 can be null; however, PCextreme\Cloudstack\Abs...Client::parseResponse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
135
136
        $this->checkResponse($response, $parsed);
0 ignored issues
show
Bug introduced by
It seems like $response defined by $this->sendRequest($request) on line 133 can be null; however, PCextreme\Cloudstack\Abs...Client::checkResponse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
Bug introduced by
It seems like $parsed defined by $this->parseResponse($response) on line 134 can also be of type null; however, PCextreme\Cloudstack\Abs...Client::checkResponse() does only seem to accept array|string, 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...
137
138
        return $parsed;
139
    }
140
141
    /**
142
     * Attempts to parse a JSON response.
143
     *
144
     * @param  string  $content
145
     * @return array
146
     * @throws UnexpectedValueException
147
     */
148
    protected function parseJson($content)
149
    {
150
        $content = json_decode($content, true);
151
152
        if (json_last_error() !== JSON_ERROR_NONE) {
153
            throw new UnexpectedValueException(sprintf(
154
                "Failed to parse JSON response: %s",
155
                json_last_error_msg()
156
            ));
157
        }
158
159
        return $content;
160
    }
161
162
    /**
163
     * Returns the content type header of a response.
164
     *
165
     * @param  ResponseInterface  $response
166
     * @return string
167
     */
168
    protected function getContentType(ResponseInterface $response)
169
    {
170
        return join(';', (array) $response->getHeader('content-type'));
171
    }
172
173
    /**
174
     * Parses the response according to its content-type header.
175
     *
176
     * @param  ResponseInterface  $response
177
     * @return array
178
     * @throws UnexpectedValueException
179
     */
180
    protected function parseResponse(ResponseInterface $response)
181
    {
182
        $content = (string) $response->getBody();
183
        $type = $this->getContentType($response);
184
185
        if (strpos($type, 'urlencoded') !== false) {
186
            parse_str($content, $parsed);
187
            return $parsed;
188
        }
189
190
        // Attempt to parse the string as JSON regardless of content type,
191
        // since some providers use non-standard content types. Only throw an
192
        // exception if the JSON could not be parsed when it was expected to.
193
        try {
194
            return $this->parseJson($content);
195
        } catch (UnexpectedValueException $e) {
196
            if (strpos($type, 'json') !== false) {
197
                throw $e;
198
            }
199
200
            return $content;
201
        }
202
    }
203
204
    /**
205
     * Checks a provider response for errors.
206
     *
207
     * @param  ResponseInterface  $response
208
     * @param  array|string       $data
209
     * @return void
210
     * @throws \PCextreme\Cloudstack\Exceptions\ClientException
211
     */
212
    abstract protected function checkResponse(ResponseInterface $response, $data);
213
214
    /**
215
     * Sets the request factory instance.
216
     *
217
     * @param  RequestFactory  $factory
218
     * @return self
219
     */
220
    public function setRequestFactory(RequestFactory $factory)
221
    {
222
        $this->requestFactory = $factory;
223
224
        return $this;
225
    }
226
227
    /**
228
     * Returns the request factory instance.
229
     *
230
     * @return RequestFactory
231
     */
232
    public function getRequestFactory()
233
    {
234
        return $this->requestFactory;
235
    }
236
237
    /**
238
     * Sets the HTTP client instance.
239
     *
240
     * @param  HttpClientInterface  $client
241
     * @return self
242
     */
243
    public function setHttpClient(HttpClientInterface $client)
244
    {
245
        $this->httpClient = $client;
246
247
        return $this;
248
    }
249
250
    /**
251
     * Returns the HTTP client instance.
252
     *
253
     * @return HttpClientInterface
254
     */
255
    public function getHttpClient()
256
    {
257
        return $this->httpClient;
258
    }
259
}
260