Completed
Pull Request — master (#59)
by Julien
04:27
created

RestClient::getCurrentRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
3
namespace Mapado\RestClientSdk;
4
5
use GuzzleHttp\ClientInterface;
6
use GuzzleHttp\Exception\ClientException;
7
use GuzzleHttp\Exception\TransferException;
8
use Mapado\RestClientSdk\Exception\RestClientException;
9
use Mapado\RestClientSdk\Exception\RestException;
10
use Psr\Http\Message\ResponseInterface;
11
use Symfony\Component\HttpFoundation\Request;
12
13
/**
14
 * Class RestClient
15
 * @author Julien Deniau <[email protected]>
16
 */
17
class RestClient
18
{
19
    /**
20
     * httpClient
21
     *
22
     * @var ClientInterface
23
     * @access private
24
     */
25
    private $httpClient;
26
27
    /**
28
     * baseUrl
29
     *
30
     * @var string
31
     * @access private
32
     */
33
    private $baseUrl;
34
35
    /**
36
     * logHistory
37
     *
38
     * @var boolean
39
     * @access private
40
     */
41
    private $logHistory;
42
43
    /**
44
     * requestHistory
45
     *
46
     * @var array
47
     * @access private
48
     */
49
    private $requestHistory;
50
51
    /**
52
     * currentRequest
53
     *
54
     * @var Request
55
     * @access private
56
     */
57
    private $currentRequest;
58
59
    /**
60
     * @param ClientInterface $httpClient
61
     * @param string|null     $baseUrl
62
     */
63
    public function __construct(ClientInterface $httpClient, $baseUrl = null)
64
    {
65
        $this->httpClient     = $httpClient;
66
        $this->baseUrl        = substr($baseUrl, -1) === '/' ? substr($baseUrl, 0, -1) : $baseUrl;
67
        $this->logHistory     = false;
68
        $this->requestHistory = [];
69
    }
70
71
    /**
72
     * @return bool
73
     */
74
    public function isHistoryLogged()
75
    {
76
        return $this->logHistory;
77
    }
78
79
    /**
80
     * setCurrentRequest
81
     *
82
     * @param Request $currentRequest
83
     * @access public
84
     * @return RestClient
85
     */
86
    public function setCurrentRequest(Request $currentRequest)
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $currentRequest. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
87
    {
88
        $this->currentRequest = $currentRequest;
89
90
        return $this;
91
    }
92
93
    /**
94
     * setLogHistory
95
     *
96
     * @param boolean $logHistory
97
     * @access public
98
     * @return RestClient
99
     */
100
    public function setLogHistory($logHistory)
101
    {
102
        $this->logHistory = $logHistory;
103
104
        return $this;
105
    }
106
107
    /**
108
     * @return array
109
     */
110
    public function getRequestHistory()
111
    {
112
        return $this->requestHistory;
113
    }
114
115
    /**
116
     * get a path
117
     *
118
     * @param string $path
119
     * @param array  $parameters
120
     * @return array|ResponseInterface|null
121
     * @throws RestException
122
     */
123
    public function get($path, $parameters = [])
124
    {
125
        $requestUrl = $this->baseUrl . $path;
126
        try {
127
            return $this->executeRequest('GET', $requestUrl, $parameters);
128
        } catch (ClientException $e) {
129
            if ($e->getResponse()->getStatusCode() === 404) {
130
                return null;
131
            }
132
            throw new RestClientException('Error while getting resource', $path, [], 7, $e);
133
        } catch (TransferException $e) {
134
            throw new RestException('Error while getting resource', $path, [], 1, $e);
135
        }
136
    }
137
138
    /**
139
     * delete
140
     *
141
     * @param string $path
142
     * @access public
143
     * @return void
144
     * @throws RestException
145
     */
146
    public function delete($path)
147
    {
148
        try {
149
            $this->executeRequest('DELETE', $this->baseUrl . $path);
150
        } catch (ClientException $e) {
151
            return;
152
        } catch (TransferException $e) {
153
            throw new RestException('Error while deleting resource', $path, [], 2, $e);
154
        }
155
    }
156
157
    /**
158
     * @param string $path
159
     * @param mixed  $data
160
     * @param array  $parameters
161
     * @return array|ResponseInterface
162
     * @throws RestClientException
163
     * @throws RestException
164
     */
165 View Code Duplication
    public function post($path, $data, $parameters = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
166
    {
167
        $parameters['json'] = $data;
168
        try {
169
            return $this->executeRequest('POST', $this->baseUrl . $path, $parameters);
170
        } catch (ClientException $e) {
171
            throw new RestClientException('Cannot create resource', $path, [], 3, $e);
172
        } catch (TransferException $e) {
173
            throw new RestException('Error while posting resource', $path, [], 4, $e);
174
        }
175
    }
176
177
    /**
178
     * @param string $path
179
     * @param mixed  $data
180
     * @param array  $parameters
181
     * @return array|ResponseInterface
182
     * @throws RestClientException
183
     * @throws RestException
184
     */
185 View Code Duplication
    public function put($path, $data, $parameters = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
186
    {
187
        $parameters['json'] = $data;
188
189
        try {
190
            return $this->executeRequest('PUT', $this->baseUrl . $path, $parameters);
191
        } catch (ClientException $e) {
192
            throw new RestClientException('Cannot update resource', $path, [], 5, $e);
193
        } catch (TransferException $e) {
194
            throw new RestException('Error while puting resource', $path, [], 6, $e);
195
        }
196
    }
197
198
    /**
199
     * Merge default parameters.
200
     *
201
     * @param array $parameters
202
     * @access protected
203
     * @return array
204
     */
205
    protected function mergeDefaultParameters(array $parameters)
206
    {
207
        $request = $this->getCurrentRequest();
208
209
        $defaultParameters = [
210
            'version' => '1.0',
211
        ];
212
213
        if ($request) {
214
            $defaultParameters['headers'] = [
215
                'referer' => $request->getUri(),
216
            ];
217
        }
218
219
        return array_replace_recursive($defaultParameters, $parameters);
220
    }
221
222
    /**
223
     * getCurrentRequest
224
     *
225
     * @access private
226
     * @return Request
227
     */
228
    protected function getCurrentRequest()
229
    {
230
        if (!$this->currentRequest) {
231
            $this->currentRequest = Request::createFromGlobals();
232
        }
233
234
        return $this->currentRequest;
235
    }
236
237
    /**
238
     * Executes request.
239
     *
240
     * @param string $method
241
     * @param string $url
242
     * @param array  $parameters
243
     * @access private
244
     * @return ResponseInterface|array
245
     * @throws TransferException
246
     */
247
    private function executeRequest($method, $url, $parameters = [])
248
    {
249
        $parameters = $this->mergeDefaultParameters($parameters);
250
251
        $startTime = null;
252
        if ($this->isHistoryLogged()) {
253
            $startTime = microtime(true);
254
        }
255
256
        try {
257
            $response = $this->httpClient->request($method, $url, $parameters);
258
            $this->logRequest($startTime, $method, $url, $parameters, $response);
259
        } catch (TransferException $e) {
260
            $this->logRequest($startTime, $method, $url, $parameters, $e->getResponse());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class GuzzleHttp\Exception\TransferException as the method getResponse() does only exist in the following sub-classes of GuzzleHttp\Exception\TransferException: GuzzleHttp\Exception\BadResponseException, GuzzleHttp\Exception\ClientException, GuzzleHttp\Exception\ConnectException, GuzzleHttp\Exception\RequestException, GuzzleHttp\Exception\ServerException, GuzzleHttp\Exception\TooManyRedirectsException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
261
            throw $e;
262
        }
263
264
        $headers = $response->getHeaders();
265
        $jsonContentTypeList = ['application/ld+json', 'application/json'];
266
267
        $requestIsJson = false;
268
269
        if (isset($headers['Content-Type'])) {
270
            foreach ($jsonContentTypeList as $contentType) {
271
                if (stripos($headers['Content-Type'][0], $contentType) !== false) {
272
                    $requestIsJson = true;
273
                    break;
274
                }
275
            }
276
        }
277
278
        if ($requestIsJson) {
279
            return json_decode($response->getBody(), true);
280
        } else {
281
            return $response;
282
        }
283
    }
284
285
    /**
286
     * Logs request.
287
     *
288
     * @param float|null             $startTime
289
     * @param string                 $method
290
     * @param string                 $url
291
     * @param array                  $parameters
292
     * @param ResponseInterface|null $response
293
     * @access private
294
     * @return void
295
     */
296
    private function logRequest($startTime, $method, $url, $parameters, ResponseInterface $response = null)
297
    {
298
        if ($this->isHistoryLogged()) {
299
            $queryTime = microtime(true) - $startTime;
300
301
            $this->requestHistory[] = [
302
                'method'       => $method,
303
                'url'          => $url,
304
                'parameters'   => $parameters,
305
                'response'     => $response,
306
                'responseBody' => $response ? json_decode($response->getBody(), true) : null,
307
                'queryTime'    => $queryTime,
308
            ];
309
        }
310
    }
311
}
312