Completed
Branch master (e62670)
by
unknown
02:05
created

BaseRestClient::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 4
dl 0
loc 6
ccs 6
cts 6
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?PHP
2
3
namespace Blocktrail\SDK\Connection;
4
5
use Blocktrail\SDK\Connection\Exceptions\BannedIP;
6
use GuzzleHttp\Psr7\Request;
7
use GuzzleHttp\Psr7\Uri;
8
use Blocktrail\SDK\Blocktrail;
9
use Blocktrail\SDK\Connection\Exceptions\EndpointSpecificError;
10
use Blocktrail\SDK\Connection\Exceptions\GenericServerError;
11
use Blocktrail\SDK\Connection\Exceptions\ObjectNotFound;
12
use Blocktrail\SDK\Connection\Exceptions\UnknownEndpointSpecificError;
13
use Blocktrail\SDK\Connection\Exceptions\EmptyResponse;
14
use Blocktrail\SDK\Connection\Exceptions\InvalidCredentials;
15
use Blocktrail\SDK\Connection\Exceptions\MissingEndpoint;
16
use Blocktrail\SDK\Connection\Exceptions\GenericHTTPError;
17
use Psr\Http\Message\ResponseInterface;
18
19
/**
20
 * Class BaseRestClient
21
 *
22
 */
23
abstract class BaseRestClient implements RestClientInterface
24
{
25
26
    const AUTH_HTTP_SIG = 'http-signatures';
27
28
    /**
29
     * @var string
30
     */
31
    protected $apiKey;
32
33
    /**
34
     * @var string
35
     */
36
    protected $apiEndpoint;
37
38
    /**
39
     * @var string
40
     */
41
    protected $apiVersion;
42
43
    /**
44
     * @var string
45
     */
46
    protected $apiSecret;
47
48
    /**
49
     * @var bool
50
     */
51
    protected $verboseErrors = false;
52
53
    /**
54
     * @var array
55
     */
56
    protected static $replaceQuery = ['=' => '%3D', '&' => '%26'];
57
58
    /**
59
     * BaseRestClient constructor.
60
     * @param string $apiEndpoint
61
     * @param string $apiVersion
62
     * @param string $apiKey
63
     * @param string $apiSecret
64
     */
65 118
    public function __construct($apiEndpoint, $apiVersion, $apiKey, $apiSecret) {
66 118
        $this->apiEndpoint = $apiEndpoint;
67 118
        $this->apiVersion = $apiVersion;
68 118
        $this->apiKey = $apiKey;
69 118
        $this->apiSecret = $apiSecret;
70 118
    }
71
72
    /**
73
     * @param Uri $uri
74
     * @param string $key
75
     * @return bool
76
     */
77 33
    public static function hasQueryValue(Uri $uri, $key) {
78 33
        $current = $uri->getQuery();
79 33
        $key = strtr($key, self::$replaceQuery);
80
81 33
        if (!$current) {
82 33
            $result = [];
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
83
        } else {
84 20
            $result = [];
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
85 20
            foreach (explode('&', $current) as $part) {
86 20
                if (explode('=', $part)[0] === $key) {
87 20
                    return true;
88
                };
89
            }
90
        }
91
92 33
        return false;
93
    }
94
95
    /**
96
     * enable verbose errors
97
     *
98
     * @param   bool        $verboseErrors
99
     */
100
    public function setVerboseErrors($verboseErrors = true) {
101
        $this->verboseErrors = $verboseErrors;
102
    }
103
104
    /**
105
     * @param   string          $endpointUrl
106
     * @param   array           $queryString
107
     * @param   string          $auth           http-signatures to enable http-signature signing
108
     * @param   float           $timeout        timeout in seconds
109
     * @return  Response
110
     */
111 31
    public function get($endpointUrl, $queryString = null, $auth = null, $timeout = null) {
112 31
        return $this->request('GET', $endpointUrl, $queryString, null, $auth, null, $timeout);
113
    }
114
115
    /**
116
     * @param   string          $endpointUrl
117
     * @param   null            $queryString
118
     * @param   array|string    $postData
119
     * @param   string          $auth           http-signatures to enable http-signature signing
120
     * @param   float           $timeout        timeout in seconds
121
     * @return  Response
122
     */
123 23
    public function post($endpointUrl, $queryString = null, $postData = '', $auth = null, $timeout = null) {
124 23
        return $this->request('POST', $endpointUrl, $queryString, $postData, $auth, null, $timeout);
125
    }
126
127
    /**
128
     * @param   string          $endpointUrl
129
     * @param   null            $queryString
130
     * @param   array|string    $putData
131
     * @param   string          $auth           http-signatures to enable http-signature signing
132
     * @param   float           $timeout        timeout in seconds
133
     * @return  Response
134
     */
135 1
    public function put($endpointUrl, $queryString = null, $putData = '', $auth = null, $timeout = null) {
136 1
        return $this->request('PUT', $endpointUrl, $queryString, $putData, $auth, null, $timeout);
137
    }
138
139
    /**
140
     * @param   string          $endpointUrl
141
     * @param   null            $queryString
142
     * @param   array|string    $postData
143
     * @param   string          $auth           http-signatures to enable http-signature signing
144
     * @param   float           $timeout        timeout in seconds
145
     * @return  Response
146
     */
147 11
    public function delete($endpointUrl, $queryString = null, $postData = null, $auth = null, $timeout = null) {
148 11
        return $this->request('DELETE', $endpointUrl, $queryString, $postData, $auth, 'url', $timeout);
149
    }
150
151
    /**
152
     * generic request executor
153
     *
154
     * @param   string          $method         GET, POST, PUT, DELETE
155
     * @param   string          $endpointUrl
156
     * @param   array           $queryString
157
     * @param   array|string    $body
158
     * @param   string          $auth           http-signatures to enable http-signature signing
159
     * @param   string          $contentMD5Mode body or url
160
     * @param   float           $timeout        timeout in seconds
161
     * @return Request
162
     */
163 33
    public function buildRequest($method, $endpointUrl, $queryString = null, $body = null, $auth = null, $contentMD5Mode = null, $timeout = null) {
164 33
        if (is_null($contentMD5Mode)) {
165 33
            $contentMD5Mode = !is_null($body) ? 'body' : 'url';
166
        }
167
168 33
        $request = new Request($method, $this->apiEndpoint . $endpointUrl);
169 33
        $uri = $request->getUri();
170
171 33
        if ($queryString) {
172 20
            foreach ($queryString as $k => $v) {
173 20
                $uri = Uri::withQueryValue($uri, $k, $v);
174
            }
175
        }
176
177 33
        if (!self::hasQueryValue($uri, 'api_key')) {
0 ignored issues
show
Compatibility introduced by
$uri of type object<Psr\Http\Message\UriInterface> is not a sub-type of object<GuzzleHttp\Psr7\Uri>. It seems like you assume a concrete implementation of the interface Psr\Http\Message\UriInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
178 33
            $uri = Uri::withQueryValue($uri, 'api_key', $this->apiKey);
179
        }
180
181
        // normalize the query string the same way the server expects it
182
        /** @var Request $request */
183 33
        $request = $request->withUri($uri->withQuery(\Symfony\Component\HttpFoundation\Request::normalizeQueryString($uri->getQuery())));
184
185 33
        if (!$request->hasHeader('Date')) {
186 33
            $request = $request->withHeader('Date', $this->getRFC1123DateString());
187
        }
188
189 33
        if (!is_null($body)) {
190 23
            if (!$request->hasHeader('Content-Type')) {
191 23
                $request = $request->withHeader('Content-Type', 'application/json');
192
            }
193
194 23
            if (!is_string($body)) {
195 23
                $body = json_encode($body);
196
            }
197 23
            $request = $request->withBody(\GuzzleHttp\Psr7\stream_for($body));
198
        }
199
200
        // for GET/DELETE requests, MD5 the request URI (excludes domain, includes query strings)
201 33
        if ($contentMD5Mode == 'body') {
202 23
            $request = $request->withHeader('Content-MD5', md5((string)$body));
203
        } else {
204 31
            $request = $request->withHeader('Content-MD5', md5($request->getRequestTarget()));
205
        }
206
207 33
        return $request;
208
    }
209
210
    /**
211
     * @param ResponseInterface $responseObj
212
     * @return Response
213
     * @throws BannedIP
214
     * @throws EmptyResponse
215
     * @throws EndpointSpecificError
216
     * @throws GenericHTTPError
217
     * @throws GenericServerError
218
     * @throws InvalidCredentials
219
     * @throws MissingEndpoint
220
     * @throws ObjectNotFound
221
     * @throws UnknownEndpointSpecificError
222
     */
223 33
    public function responseHandler(ResponseInterface $responseObj) {
224 33
        $httpResponseCode = (int)$responseObj->getStatusCode();
225 33
        $httpResponsePhrase = (string)$responseObj->getReasonPhrase();
226 33
        $body = $responseObj->getBody();
227
228 33
        if ($httpResponseCode == 200) {
229 33
            if (!$body) {
230
                throw new EmptyResponse(Blocktrail::EXCEPTION_EMPTY_RESPONSE, $httpResponseCode);
231
            }
232
233 33
            $result = new Response($httpResponseCode, $body);
234
235 33
            return $result;
236 9
        } elseif ($httpResponseCode == 400 || $httpResponseCode == 403) {
237 6
            $data = json_decode($body, true);
238
239 6
            if ($data && isset($data['msg'], $data['code'])) {
240 6
                throw new EndpointSpecificError(!is_string($data['msg']) ? json_encode($data['msg']) : $data['msg'], $data['code']);
241
            } else {
242
                if (preg_match("/^banned( IP)? \[(.+)\]\n?$/", $body, $m)) {
243
                    throw new BannedIP($m[2]);
244
                }
245
                throw new UnknownEndpointSpecificError($this->verboseErrors ? $body : Blocktrail::EXCEPTION_UNKNOWN_ENDPOINT_SPECIFIC_ERROR);
246
            }
247 9
        } elseif ($httpResponseCode == 401) {
248 1
            throw new InvalidCredentials($this->verboseErrors ? $body : Blocktrail::EXCEPTION_INVALID_CREDENTIALS, $httpResponseCode);
249 8
        } elseif ($httpResponseCode == 404) {
250 8
            if ($httpResponsePhrase == "Endpoint Not Found") {
251
                throw new MissingEndpoint($this->verboseErrors ? $body : Blocktrail::EXCEPTION_MISSING_ENDPOINT, $httpResponseCode);
252
            } else {
253 8
                throw new ObjectNotFound($this->verboseErrors ? $body : Blocktrail::EXCEPTION_OBJECT_NOT_FOUND, $httpResponseCode);
254
            }
255
        } elseif ($httpResponseCode == 500) {
256
            throw new GenericServerError(Blocktrail::EXCEPTION_GENERIC_SERVER_ERROR . "\nServer Response: " . $body, $httpResponseCode);
257
        } else {
258
            throw new GenericHTTPError(Blocktrail::EXCEPTION_GENERIC_HTTP_ERROR . "\nServer Response: " . $body, $httpResponseCode);
259
        }
260
    }
261
262
    /**
263
     * Returns curent fate time in RFC1123 format, using UTC time zone
264
     *
265
     * @return  string
266
     */
267 33
    private function getRFC1123DateString() {
268 33
        $date = new \DateTime(null, new \DateTimeZone("UTC"));
269 33
        return str_replace("+0000", "GMT", $date->format(\DateTime::RFC1123));
270
    }
271
}
272