Completed
Push — master ( 876719...fea7ae )
by Adam
01:59
created

GuzzleClient::sendRecord()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 0
cts 5
cp 0
rs 9.9
c 0
b 0
f 0
cc 2
nc 1
nop 5
crap 6
1
<?php
2
/**
3
 * GuzzleClient.php
4
 *
5
 * @copyright      More in license.md
6
 * @license        https://www.ipublikuj.eu
7
 * @author         Adam Kadlec <[email protected]>
8
 * @package        iPublikuj:JsonAPIClient!
9
 * @subpackage     Clients
10
 * @since          1.0.0
11
 *
12
 * @date           05.05.18
13
 */
14
15
declare(strict_types = 1);
16
17
namespace IPub\JsonAPIClient\Clients;
18
19
use Nette;
20
use Nette\Http as NHttp;
21
use Nette\Utils;
22
23
use GuzzleHttp;
24
use GuzzleHttp\Client;
25
use GuzzleHttp\Exception\BadResponseException;
26
use GuzzleHttp\Psr7\Request;
27
28
use Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface;
29
use Neomerx\JsonApi\Encoder\EncoderOptions;
30
use Neomerx\JsonApi\Exceptions\JsonApiException;
31
use Neomerx\JsonApi\Factories\Factory;
32
33
use Psr\Http\Message\RequestInterface as PsrRequest;
34
use Psr\Http\Message\ResponseInterface as PsrResponse;
35
36
use IPub\JsonAPIClient\Encoders;
37
use IPub\JsonAPIClient\Exceptions;
38
use IPub\JsonAPIClient\Http;
39
use IPub\JsonAPIClient\Objects;
40
use IPub\JsonAPIClient\Schemas;
41
42
/**
43
 * Guzzle client service
44
 *
45
 * @package        iPublikuj:JsonAPIClient!
46
 * @subpackage     Clients
47
 *
48
 * @author         Adam Kadlec <[email protected]>
49
 */
50 1
class GuzzleClient implements IClient
51
{
52
	/**
53
	 * Implement nette smart magic
54
	 */
55 1
	use Nette\SmartObject;
56
57 1
	use TSendsRequests;
58
59
	/**
60
	 * Additional request headers
61
	 *
62
	 * @var string[]
63
	 */
64
	private $headers = [];
65
66
	/**
67
	 * @var Client
68
	 */
69
	private $http;
70
71
	/**
72
	 * @param string|NULL $baseUri
73
	 * @param Schemas\SchemaProvider $schemaProvider
74
	 * @param Client|NULL $client
75
	 */
76
	public function __construct(
77
		?string $baseUri = NULL,
78
		Schemas\SchemaProvider $schemaProvider,
79
		Client $client = NULL
80
	) {
81 1
		if ($client === NULL && $baseUri === NULL) {
82
			throw new Exceptions\InvalidStateException('You have to define base_uri or client to be able use api client.');
83
		}
84
85 1
		$this->http = $client !== NULL ? $client : $this->createClient($baseUri);
86
87 1
		$factory = new Factory;
88 1
		$this->schemas = $factory->createContainer($schemaProvider->getMapping());
89 1
		$this->serializer = new Encoders\Encoder($factory, $this->schemas, new EncoderOptions(
90 1
			JSON_PRETTY_PRINT
91
		));
92 1
	}
93
94
	/**
95
	 * {@inheritdoc}
96
	 */
97
	public function index(string $endpoint, EncodingParametersInterface $parameters = NULL, array $options = []) : Http\IResponse
98
	{
99
		$options = $this->mergeOptions([
100
			GuzzleHttp\RequestOptions::HEADERS => $this->jsonApiHeaders(FALSE),
101
			GuzzleHttp\RequestOptions::QUERY   => $parameters ? $this->parseSearchQuery($parameters) : NULL,
102
		], $options);
103
104
		return $this->request(NHttp\IRequest::GET, $endpoint, $options);
105
	}
106
107
	/**
108
	 * {@inheritdoc}
109
	 */
110
	public function read(string $endpoint, EncodingParametersInterface $parameters = NULL, array $options = []) : Http\IResponse
111
	{
112
		$options = $this->mergeOptions([
113
			GuzzleHttp\RequestOptions::HEADERS => $this->jsonApiHeaders(FALSE),
114
			GuzzleHttp\RequestOptions::QUERY   => $parameters ? $this->parseQuery($parameters) : NULL,
115
		], $options);
116
117
		return $this->request(NHttp\IRequest::GET, $endpoint, $options);
118
	}
119
120
	/**
121
	 * {@inheritdoc}
122
	 */
123
	public function create(string $endpoint, $record, EncodingParametersInterface $parameters = NULL, array $options = []) : Http\IResponse
124
	{
125
		if (!is_object($record)) {
126
			throw new Exceptions\InvalidArgumentException('Provided data entity is not an object.');
127
		}
128
129
		return $this->sendRecord($endpoint, NHttp\IRequest::POST, $this->serializeRecord($record), $parameters, $options);
130
	}
131
132
	/**
133
	 * {@inheritdoc}
134
	 */
135
	public function update(string $endpoint, $record, array $fields = NULL, EncodingParametersInterface $parameters = NULL, array $options = []) : Http\IResponse
136
	{
137
		if (!is_object($record)) {
138
			throw new Exceptions\InvalidArgumentException('Provided data entity is not an object.');
139
		}
140
141
		return $this->sendRecord($endpoint, NHttp\IRequest::PATCH, $this->serializeRecord($record, $fields), $parameters, $options);
142
	}
143
144
	/**
145
	 * {@inheritdoc}
146
	 */
147
	public function delete(string $endpoint, array $options = []) : Http\IResponse
148
	{
149
		$options = $this->mergeOptions([
150
			GuzzleHttp\RequestOptions::HEADERS => $this->jsonApiHeaders(FALSE)
151
		], $options);
152
153
		return $this->request(NHttp\IRequest::DELETE, $endpoint, $options);
154
	}
155
156
	/**
157
	 * {@inheritdoc}
158
	 */
159
	public function addApiKey(string $key) : void
160
	{
161
		$this->addHeader('X-Api-Key', $key);
162
	}
163
164
	/**
165
	 * {@inheritdoc}
166
	 */
167
	public function addAuthorization(string $token) : void
168
	{
169
		$this->addHeader('Authorization', 'Bearer ' . $token);
170
	}
171
172
	/**
173
	 * {@inheritdoc}
174
	 */
175
	public function removeAuthorization() : void
176
	{
177
		if (isset($this->headers['Authorization'])) {
178
			unset($this->headers['Authorization']);
179
		}
180
	}
181
182
	/**
183
	 * {@inheritdoc}
184
	 */
185
	public function addHeader(string $header, string $value) : void
186
	{
187
		$this->headers[$header] = $value;
188
	}
189
190
	/**
191
	 * @param string $endpoint
192
	 * @param string $method
193
	 * @param array $serializedRecord the encoded record
194
	 * @param EncodingParametersInterface|NULL $parameters
195
	 * @param array $options
196
	 *
197
	 * @return Http\IResponse
198
	 *
199
	 * @throws JsonApiException
200
	 * @throws GuzzleHttp\Exception\GuzzleException
201
	 * @throws Utils\JsonException
202
	 */
203
	protected function sendRecord(string $endpoint, string $method, array $serializedRecord, EncodingParametersInterface $parameters = NULL, array $options = []) : Http\IResponse
204
	{
205
		$options = $this->mergeOptions([
206
			GuzzleHttp\RequestOptions::HEADERS => $this->jsonApiHeaders(TRUE),
207
			GuzzleHttp\RequestOptions::QUERY   => $parameters ? $this->parseQuery($parameters) : NULL,
208
		], $options);
209
210
		$options['json'] = $serializedRecord;
211
212
		return $this->request($method, $endpoint, $options);
213
	}
214
215
	/**
216
	 * @param array $new
217
	 * @param array $existing
218
	 *
219
	 * @return array
220
	 */
221
	protected function mergeOptions(array $new, array $existing) : array
222
	{
223
		return array_replace_recursive($new, $existing);
224
	}
225
226
	/**
227
	 * @param string $method
228
	 * @param string $uri
229
	 * @param array $options
230
	 *
231
	 * @return Http\IResponse
232
	 *
233
	 * @throws JsonApiException
234
	 * @throws GuzzleHttp\Exception\GuzzleException
235
	 * @throws Utils\JsonException
236
	 */
237
	protected function request(string $method, string $uri, array $options = []) : Http\IResponse
238
	{
239
		$request = new Request($method, $uri);
240
241
		try {
242
			$response = $this->http->send($request, $options);
243
244
		} catch (BadResponseException $ex) {
0 ignored issues
show
Bug introduced by
The class GuzzleHttp\Exception\BadResponseException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
245
			throw $this->parseErrorResponse($request, $ex);
246
		}
247
248
		return new Http\Response($response, $this->createDocumentObject($request, $response));
249
	}
250
251
	/**
252
	 * Safely parse an error response.
253
	 *
254
	 * This method wraps decoding the body content of the provided exception, so that
255
	 * another exception is not thrown while trying to parse an existing exception.
256
	 *
257
	 * @param PsrRequest $request
258
	 * @param BadResponseException $ex
259
	 *
260
	 * @return JsonApiException
261
	 */
262
	private function parseErrorResponse(PsrRequest $request, BadResponseException $ex) : JsonApiException
263
	{
264
		try {
265
			$response = $ex->getResponse();
266
267
			$document = $response ? $this->createDocumentObject($request, $response) : NULL;
268
269
			$errors = $document && $document->getErrors() !== NULL ? $document->getErrors() : [$this->createErrorObject($request, $response)];
270
271
			$statusCode = $response ? $response->getStatusCode() : 0;
272
273
		} catch (\Exception $e) {
274
			$errors = [];
275
			$statusCode = 0;
276
		}
277
278
		return new JsonApiException($errors, $statusCode, $ex);
279
	}
280
281
	/**
282
	 * @param PsrRequest $request
283
	 * @param PsrResponse|NULL $response
284
	 *
285
	 * @return Objects\IDocument|NULL
286
	 *
287
	 * @throws Utils\JsonException
288
	 */
289
	private function createDocumentObject(PsrRequest $request, PsrResponse $response = NULL) : ?Objects\IDocument
290
	{
291
		return new Objects\Document(Utils\Json::decode(($response ? (string) $response->getBody() : (string) $request->getBody())));
292
	}
293
294
	/**
295
	 * @param PsrRequest $request
296
	 * @param PsrResponse|NULL $response
297
	 *
298
	 * @return Objects\IMutableError|NULL
299
	 *
300
	 * @throws Utils\JsonException
301
	 */
302
	private function createErrorObject(PsrRequest $request, PsrResponse $response = NULL) : ?Objects\IMutableError
303
	{
304
		return Objects\Error::create(Utils\Json::decode(($response ? (string) $response->getBody() : (string) $request->getBody()), Utils\Json::FORCE_ARRAY));
305
	}
306
307
	/**
308
	 * @param string $baseUri
309
	 *
310
	 * @return Client
311
	 */
312
	private function createClient(string $baseUri) : Client
313
	{
314 1
		$client = new Client([
315 1
			'base_uri' => $baseUri,
316
		]);
317
318 1
		return $client;
319
	}
320
}
321