Completed
Push — master ( 0ef30a...eb3939 )
by Kevin
03:35
created

AbstractClient::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 16
rs 9.4285
cc 3
eloc 9
nc 4
nop 2
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
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
45
     */
46
    public function __construct(array $options = [], array $collaborators = [])
47
    {
48
        if (empty($collaborators['requestFactory'])) {
49
            $collaborators['requestFactory'] = new RequestFactory();
50
        }
51
        $this->setRequestFactory($collaborators['requestFactory']);
52
53
        if (empty($collaborators['httpClient'])) {
54
            $clientOptions = $this->getAllowedClientOptions($options);
55
56
            $collaborators['httpClient'] = new HttpClient(
57
                array_intersect_key($options, array_flip($clientOptions))
58
            );
59
        }
60
        $this->setHttpClient($collaborators['httpClient']);
61
    }
62
63
    /**
64
     * Return the list of options that can be passed to the HttpClient
65
     *
66
     * @param  array  $options
67
     * @return array
68
     */
69
    protected function getAllowedClientOptions(array $options)
70
    {
71
        $clientOptions = ['timeout', 'proxy'];
72
73
        // Only allow turning off ssl verification is it's for a proxy
74
        if (! empty($options['proxy'])) {
75
            $clientOptions[] = 'verify';
76
        }
77
78
        return $clientOptions;
79
    }
80
81
    /**
82
     * Returns a PSR-7 request instance that is not authenticated.
83
     *
84
     * @param  string  $method
85
     * @param  string  $url
86
     * @param  array   $options
87
     * @return RequestInterface
88
     */
89
    public function getRequest($method, $url, array $options = [])
90
    {
91
        return $this->createRequest($method, $url, $options);
92
    }
93
94
    /**
95
     * Creates a PSR-7 request instance.
96
     *
97
     * @param  string  $method
98
     * @param  string  $url
99
     * @param  array   $options
100
     * @return RequestInterface
101
     */
102
    protected function createRequest($method, $url, array $options)
103
    {
104
        $factory = $this->getRequestFactory();
105
106
        return $factory->getRequestWithOptions($method, $url, $options);
107
    }
108
109
    /**
110
     * Sends a request instance and returns a response instance.
111
     *
112
     * @param  RequestInterface  $request
113
     * @return ResponseInterface
114
     */
115
    protected function sendRequest(RequestInterface $request)
116
    {
117
        try {
118
            $response = $this->getHttpClient()->send($request);
119
        } catch (BadResponseException $e) {
120
            $response = $e->getResponse();
121
        }
122
123
        return $response;
124
    }
125
126
    /**
127
     * Sends a request and returns the parsed response.
128
     *
129
     * @param  RequestInterface  $request
130
     * @return mixed
131
     */
132
    public function getResponse(RequestInterface $request)
133
    {
134
        $response = $this->sendRequest($request);
135
        $parsed = $this->parseResponse($response);
0 ignored issues
show
Bug introduced by
It seems like $response defined by $this->sendRequest($request) on line 134 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...
136
137
        $this->checkResponse($response, $parsed);
0 ignored issues
show
Bug introduced by
It seems like $response defined by $this->sendRequest($request) on line 134 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 135 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...
138
139
        return $parsed;
140
    }
141
142
    /**
143
     * Attempts to parse a JSON response.
144
     *
145
     * @param  string  $content
146
     * @return array
147
     * @throws UnexpectedValueException
148
     */
149
    protected function parseJson($content)
150
    {
151
        $content = json_decode($content, true);
152
153
        if (json_last_error() !== JSON_ERROR_NONE) {
154
            throw new UnexpectedValueException(sprintf(
155
                "Failed to parse JSON response: %s",
156
                json_last_error_msg()
157
            ));
158
        }
159
160
        return $content;
161
    }
162
163
    /**
164
     * Returns the content type header of a response.
165
     *
166
     * @param  ResponseInterface  $response
167
     * @return string
168
     */
169
    protected function getContentType(ResponseInterface $response)
170
    {
171
        return join(';', (array) $response->getHeader('content-type'));
172
    }
173
174
    /**
175
     * Parses the response according to its content-type header.
176
     *
177
     * @param  ResponseInterface  $response
178
     * @return array
179
     * @throws UnexpectedValueException
180
     */
181
    protected function parseResponse(ResponseInterface $response)
182
    {
183
        $content = (string) $response->getBody();
184
        $type = $this->getContentType($response);
185
186
        if (strpos($type, 'urlencoded') !== false) {
187
            parse_str($content, $parsed);
188
            return $parsed;
189
        }
190
191
        // Attempt to parse the string as JSON regardless of content type,
192
        // since some providers use non-standard content types. Only throw an
193
        // exception if the JSON could not be parsed when it was expected to.
194
        try {
195
            return $this->parseJson($content);
196
        } catch (UnexpectedValueException $e) {
197
            if (strpos($type, 'json') !== false) {
198
                throw $e;
199
            }
200
201
            return $content;
202
        }
203
    }
204
205
    /**
206
     * Checks a provider response for errors.
207
     *
208
     * @param  ResponseInterface  $response
209
     * @param  array|string       $data
210
     * @return void
211
     * @throws \PCextreme\Cloudstack\Exceptions\ClientException
212
     */
213
    abstract protected function checkResponse(ResponseInterface $response, $data);
214
215
    /**
216
     * Sets the request factory instance.
217
     *
218
     * @param  RequestFactory  $factory
219
     * @return self
220
     */
221
    public function setRequestFactory(RequestFactory $factory)
222
    {
223
        $this->requestFactory = $factory;
224
225
        return $this;
226
    }
227
228
    /**
229
     * Returns the request factory instance.
230
     *
231
     * @return RequestFactory
232
     */
233
    public function getRequestFactory()
234
    {
235
        return $this->requestFactory;
236
    }
237
238
    /**
239
     * Sets the HTTP client instance.
240
     *
241
     * @param  HttpClientInterface  $client
242
     * @return self
243
     */
244
    public function setHttpClient(HttpClientInterface $client)
245
    {
246
        $this->httpClient = $client;
247
248
        return $this;
249
    }
250
251
    /**
252
     * Returns the HTTP client instance.
253
     *
254
     * @return HttpClientInterface
255
     */
256
    public function getHttpClient()
257
    {
258
        return $this->httpClient;
259
    }
260
}
261