1: <?php
2: namespace Worldline\Connect\Sdk;
3:
4: use Exception;
5: use UnexpectedValueException;
6: use Worldline\Connect\Sdk\Authentication\Authenticator;
7: use Worldline\Connect\Sdk\Authentication\V1HMACAuthenticator;
8: use Worldline\Connect\Sdk\Communication\Connection;
9: use Worldline\Connect\Sdk\Communication\ConnectionResponse;
10: use Worldline\Connect\Sdk\Communication\DefaultConnection;
11: use Worldline\Connect\Sdk\Communication\ErrorResponseException;
12: use Worldline\Connect\Sdk\Communication\MetadataProvider;
13: use Worldline\Connect\Sdk\Communication\MultipartDataObject;
14: use Worldline\Connect\Sdk\Communication\MultipartFormDataObject;
15: use Worldline\Connect\Sdk\Communication\RequestObject;
16: use Worldline\Connect\Sdk\Communication\ResponseBuilder;
17: use Worldline\Connect\Sdk\Communication\ResponseClassMap;
18: use Worldline\Connect\Sdk\Communication\ResponseFactory;
19: use Worldline\Connect\Sdk\Domain\DataObject;
20: use Worldline\Connect\Sdk\Logging\CommunicatorLogger;
21:
22: /**
23: * Class Communicator
24: *
25: * @package Worldline\Connect\Sdk
26: */
27: class Communicator
28: {
29: const MIME_APPLICATION_JSON = 'application/json';
30:
31: /** @var string */
32: private $apiEndpoint;
33:
34: /** @var Connection */
35: private $connection;
36:
37: /** @var Authenticator */
38: private $authenticator;
39:
40: /** @var MetadataProvider */
41: private $metadataProvider;
42:
43: /** @var ResponseFactory|null */
44: private $responseFactory = null;
45:
46: /**
47: * @param CommunicatorConfiguration $communicatorConfiguration
48: * @param Connection|null $connection
49: * @param Authenticator|null $authenticator
50: */
51: public function __construct(
52: CommunicatorConfiguration $communicatorConfiguration,
53: Authenticator $authenticator = null,
54: Connection $connection = null
55: ) {
56: $this->apiEndpoint = $communicatorConfiguration->getApiEndpoint();
57: $this->connection = $connection != null ? $connection : new DefaultConnection($communicatorConfiguration);
58: $this->authenticator = $authenticator != null ? $authenticator : new V1HMACAuthenticator($communicatorConfiguration);
59: $this->metadataProvider = new MetadataProvider($communicatorConfiguration);
60: }
61:
62: /**
63: * @param CommunicatorLogger $communicatorLogger
64: */
65: public function enableLogging(CommunicatorLogger $communicatorLogger)
66: {
67: $this->connection->enableLogging($communicatorLogger);
68: }
69:
70: /**
71: *
72: */
73: public function disableLogging()
74: {
75: $this->connection->disableLogging();
76: }
77:
78: /**
79: * @param ResponseClassMap $responseClassMap
80: * @param string $relativeUriPath
81: * @param string $clientMetaInfo
82: * @param RequestObject|null $requestParameters
83: * @param CallContext $callContext
84: * @return DataObject
85: * @throws Exception
86: */
87: public function get(
88: ResponseClassMap $responseClassMap,
89: $relativeUriPath,
90: $clientMetaInfo = '',
91: RequestObject $requestParameters = null,
92: CallContext $callContext = null
93: ) {
94: $relativeUriPathWithRequestParameters =
95: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
96: $requestHeaders =
97: $this->getRequestHeaders('GET', $relativeUriPathWithRequestParameters, static::MIME_APPLICATION_JSON, $clientMetaInfo, $callContext);
98:
99: $responseBuilder = new ResponseBuilder();
100: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder) {
101: $responseBuilder->setHttpStatusCode($httpStatusCode);
102: $responseBuilder->setHeaders($headers);
103: $responseBuilder->appendBody($data);
104: };
105:
106: $this->connection->get(
107: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
108: $requestHeaders,
109: $responseHandler
110: );
111: $connectionResponse = $responseBuilder->getResponse();
112: $this->updateCallContext($connectionResponse, $callContext);
113: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
114: $httpStatusCode = $connectionResponse->getHttpStatusCode();
115: if ($httpStatusCode >= 400) {
116: throw new ErrorResponseException($httpStatusCode, $response);
117: }
118: return $response;
119: }
120:
121: /**
122: * @param callable $bodyHandler Callable accepting a response body chunk and the response headers
123: * @param ResponseClassMap $responseClassMap Used for error handling
124: * @param string $relativeUriPath
125: * @param string $clientMetaInfo
126: * @param RequestObject|null $requestParameters
127: * @param CallContext $callContext
128: * @throws Exception
129: */
130: public function getWithBinaryResponse(
131: callable $bodyHandler,
132: ResponseClassMap $responseClassMap,
133: $relativeUriPath,
134: $clientMetaInfo = '',
135: RequestObject $requestParameters = null,
136: CallContext $callContext = null
137: ) {
138: $relativeUriPathWithRequestParameters =
139: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
140: $requestHeaders =
141: $this->getRequestHeaders('GET', $relativeUriPathWithRequestParameters, static::MIME_APPLICATION_JSON, $clientMetaInfo, $callContext);
142:
143: $responseBuilder = new ResponseBuilder();
144: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder, $bodyHandler) {
145: $responseBuilder->setHttpStatusCode($httpStatusCode);
146: $responseBuilder->setHeaders($headers);
147: if ($httpStatusCode >= 400) {
148: $responseBuilder->appendBody($data);
149: } else {
150: call_user_func($bodyHandler, $data, $headers);
151: }
152: };
153:
154: $this->connection->get(
155: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
156: $requestHeaders,
157: $responseHandler
158: );
159: $connectionResponse = $responseBuilder->getResponse();
160: $this->updateCallContext($connectionResponse, $callContext);
161: $httpStatusCode = $connectionResponse->getHttpStatusCode();
162: if ($httpStatusCode >= 400) {
163: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
164: throw new ErrorResponseException($httpStatusCode, $response);
165: }
166: }
167:
168: /**
169: * @param ResponseClassMap $responseClassMap
170: * @param string $relativeUriPath
171: * @param string $clientMetaInfo
172: * @param RequestObject|null $requestParameters
173: * @param CallContext $callContext
174: * @return DataObject
175: * @throws Exception
176: */
177: public function delete(
178: ResponseClassMap $responseClassMap,
179: $relativeUriPath,
180: $clientMetaInfo = '',
181: RequestObject $requestParameters = null,
182: CallContext $callContext = null
183: ) {
184: $relativeUriPathWithRequestParameters =
185: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
186: $requestHeaders =
187: $this->getRequestHeaders('DELETE', $relativeUriPathWithRequestParameters, static::MIME_APPLICATION_JSON, $clientMetaInfo, $callContext);
188:
189: $responseBuilder = new ResponseBuilder();
190: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder) {
191: $responseBuilder->setHttpStatusCode($httpStatusCode);
192: $responseBuilder->setHeaders($headers);
193: $responseBuilder->appendBody($data);
194: };
195:
196: $this->connection->delete(
197: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
198: $requestHeaders,
199: $responseHandler
200: );
201: $connectionResponse = $responseBuilder->getResponse();
202: $this->updateCallContext($connectionResponse, $callContext);
203: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
204: $httpStatusCode = $connectionResponse->getHttpStatusCode();
205: if ($httpStatusCode >= 400) {
206: throw new ErrorResponseException($httpStatusCode, $response);
207: }
208: return $response;
209: }
210:
211: /**
212: * @param callable $bodyHandler Callable accepting a response body chunk and the response headers
213: * @param ResponseClassMap $responseClassMap Used for error handling
214: * @param string $relativeUriPath
215: * @param string $clientMetaInfo
216: * @param RequestObject|null $requestParameters
217: * @param CallContext $callContext
218: * @throws Exception
219: */
220: public function deleteWithBinaryResponse(
221: callable $bodyHandler,
222: ResponseClassMap $responseClassMap,
223: $relativeUriPath,
224: $clientMetaInfo = '',
225: RequestObject $requestParameters = null,
226: CallContext $callContext = null
227: ) {
228: $relativeUriPathWithRequestParameters =
229: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
230: $requestHeaders =
231: $this->getRequestHeaders('DELETE', $relativeUriPathWithRequestParameters, static::MIME_APPLICATION_JSON, $clientMetaInfo, $callContext);
232:
233: $responseBuilder = new ResponseBuilder();
234: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder, $bodyHandler) {
235: $responseBuilder->setHttpStatusCode($httpStatusCode);
236: $responseBuilder->setHeaders($headers);
237: if ($httpStatusCode >= 400) {
238: $responseBuilder->appendBody($data);
239: } else {
240: call_user_func($bodyHandler, $data, $headers);
241: }
242: };
243:
244: $this->connection->delete(
245: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
246: $requestHeaders,
247: $responseHandler
248: );
249: $connectionResponse = $responseBuilder->getResponse();
250: $this->updateCallContext($connectionResponse, $callContext);
251: $httpStatusCode = $connectionResponse->getHttpStatusCode();
252: if ($httpStatusCode >= 400) {
253: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
254: throw new ErrorResponseException($httpStatusCode, $response);
255: }
256: }
257:
258: /**
259: * @param ResponseClassMap $responseClassMap
260: * @param string $relativeUriPath
261: * @param string $clientMetaInfo
262: * @param DataObject|MultipartDataObject|MultipartFormDataObject|null $requestBodyObject
263: * @param RequestObject|null $requestParameters
264: * @param CallContext $callContext
265: * @return DataObject
266: * @throws Exception
267: */
268: public function post(
269: ResponseClassMap $responseClassMap,
270: $relativeUriPath,
271: $clientMetaInfo = '',
272: $requestBodyObject = null,
273: RequestObject $requestParameters = null,
274: CallContext $callContext = null
275: ) {
276: $relativeUriPathWithRequestParameters =
277: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
278: if ($requestBodyObject instanceof MultipartFormDataObject) {
279: $contentType = $requestBodyObject->getContentType();
280: $requestBody = $requestBodyObject;
281: } elseif ($requestBodyObject instanceof MultipartDataObject) {
282: $multipart = $requestBodyObject->toMultipartFormDataObject();
283: $contentType = $multipart->getContentType();
284: $requestBody = $multipart;
285: } elseif ($requestBodyObject instanceof DataObject || is_null($requestBodyObject)) {
286: $contentType = static::MIME_APPLICATION_JSON;
287: $requestBody = $requestBodyObject ? $requestBodyObject->toJson() : '';
288: } else {
289: throw new UnexpectedValueException('Unsupported request body');
290: }
291: $requestHeaders =
292: $this->getRequestHeaders('POST', $relativeUriPathWithRequestParameters, $contentType, $clientMetaInfo, $callContext);
293:
294: $responseBuilder = new ResponseBuilder();
295: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder) {
296: $responseBuilder->setHttpStatusCode($httpStatusCode);
297: $responseBuilder->setHeaders($headers);
298: $responseBuilder->appendBody($data);
299: };
300:
301: $this->connection->post(
302: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
303: $requestHeaders,
304: $requestBody,
305: $responseHandler
306: );
307: $connectionResponse = $responseBuilder->getResponse();
308: $this->updateCallContext($connectionResponse, $callContext);
309: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
310: $httpStatusCode = $connectionResponse->getHttpStatusCode();
311: if ($httpStatusCode >= 400) {
312: throw new ErrorResponseException($httpStatusCode, $response);
313: }
314: return $response;
315: }
316:
317: /**
318: * @param callable $bodyHandler Callable accepting a response body chunk and the response headers
319: * @param ResponseClassMap $responseClassMap Used for error handling
320: * @param string $relativeUriPath
321: * @param string $clientMetaInfo
322: * @param DataObject|MultipartDataObject|MultipartFormDataObject|null $requestBodyObject
323: * @param RequestObject|null $requestParameters
324: * @param CallContext $callContext
325: * @throws Exception
326: */
327: public function postWithBinaryResponse(
328: callable $bodyHandler,
329: ResponseClassMap $responseClassMap,
330: $relativeUriPath,
331: $clientMetaInfo = '',
332: $requestBodyObject = null,
333: RequestObject $requestParameters = null,
334: CallContext $callContext = null
335: ) {
336: $relativeUriPathWithRequestParameters =
337: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
338: if ($requestBodyObject instanceof MultipartFormDataObject) {
339: $contentType = $requestBodyObject->getContentType();
340: $requestBody = $requestBodyObject;
341: } elseif ($requestBodyObject instanceof MultipartDataObject) {
342: $multipart = $requestBodyObject->toMultipartFormDataObject();
343: $contentType = $multipart->getContentType();
344: $requestBody = $multipart;
345: } elseif ($requestBodyObject instanceof DataObject || is_null($requestBodyObject)) {
346: $contentType = static::MIME_APPLICATION_JSON;
347: $requestBody = $requestBodyObject ? $requestBodyObject->toJson() : '';
348: } else {
349: throw new UnexpectedValueException('Unsupported request body');
350: }
351: $requestHeaders =
352: $this->getRequestHeaders('POST', $relativeUriPathWithRequestParameters, $contentType, $clientMetaInfo, $callContext);
353:
354: $responseBuilder = new ResponseBuilder();
355: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder, $bodyHandler) {
356: $responseBuilder->setHttpStatusCode($httpStatusCode);
357: $responseBuilder->setHeaders($headers);
358: if ($httpStatusCode >= 400) {
359: $responseBuilder->appendBody($data);
360: } else {
361: call_user_func($bodyHandler, $data, $headers);
362: }
363: };
364:
365: $this->connection->post(
366: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
367: $requestHeaders,
368: $requestBody,
369: $responseHandler
370: );
371: $connectionResponse = $responseBuilder->getResponse();
372: $this->updateCallContext($connectionResponse, $callContext);
373: $httpStatusCode = $connectionResponse->getHttpStatusCode();
374: if ($httpStatusCode >= 400) {
375: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
376: throw new ErrorResponseException($httpStatusCode, $response);
377: }
378: }
379:
380: /**
381: * @param ResponseClassMap $responseClassMap
382: * @param string $relativeUriPath
383: * @param string $clientMetaInfo
384: * @param DataObject|MultipartDataObject|MultipartFormDataObject|null $requestBodyObject
385: * @param RequestObject|null $requestParameters
386: * @param CallContext $callContext
387: * @return DataObject
388: * @throws Exception
389: */
390: public function put(
391: ResponseClassMap $responseClassMap,
392: $relativeUriPath,
393: $clientMetaInfo = '',
394: $requestBodyObject = null,
395: RequestObject $requestParameters = null,
396: CallContext $callContext = null
397: ) {
398: $relativeUriPathWithRequestParameters =
399: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
400: if ($requestBodyObject instanceof MultipartFormDataObject) {
401: $contentType = $requestBodyObject->getContentType();
402: $requestBody = $requestBodyObject;
403: } elseif ($requestBodyObject instanceof MultipartDataObject) {
404: $multipart = $requestBodyObject->toMultipartFormDataObject();
405: $contentType = $multipart->getContentType();
406: $requestBody = $multipart;
407: } elseif ($requestBodyObject instanceof DataObject || is_null($requestBodyObject)) {
408: $contentType = static::MIME_APPLICATION_JSON;
409: $requestBody = $requestBodyObject ? $requestBodyObject->toJson() : '';
410: } else {
411: throw new UnexpectedValueException('Unsupported request body');
412: }
413: $requestHeaders =
414: $this->getRequestHeaders('PUT', $relativeUriPathWithRequestParameters, $contentType, $clientMetaInfo, $callContext);
415:
416: $responseBuilder = new ResponseBuilder();
417: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder) {
418: $responseBuilder->setHttpStatusCode($httpStatusCode);
419: $responseBuilder->setHeaders($headers);
420: $responseBuilder->appendBody($data);
421: };
422:
423: $this->connection->put(
424: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
425: $requestHeaders,
426: $requestBody,
427: $responseHandler
428: );
429: $connectionResponse = $responseBuilder->getResponse();
430: $this->updateCallContext($connectionResponse, $callContext);
431: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
432: $httpStatusCode = $connectionResponse->getHttpStatusCode();
433: if ($httpStatusCode >= 400) {
434: throw new ErrorResponseException($httpStatusCode, $response);
435: }
436: return $response;
437: }
438:
439: /**
440: * @param callable $bodyHandler Callable accepting a response body chunk and the response headers
441: * @param ResponseClassMap $responseClassMap Used for error handling
442: * @param string $relativeUriPath
443: * @param string $clientMetaInfo
444: * @param DataObject|MultipartDataObject|MultipartFormDataObject|null $requestBodyObject
445: * @param RequestObject|null $requestParameters
446: * @param CallContext $callContext
447: * @throws Exception
448: */
449: public function putWithBinaryResponse(
450: callable $bodyHandler,
451: ResponseClassMap $responseClassMap,
452: $relativeUriPath,
453: $clientMetaInfo = '',
454: $requestBodyObject = null,
455: RequestObject $requestParameters = null,
456: CallContext $callContext = null
457: ) {
458: $relativeUriPathWithRequestParameters =
459: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
460: if ($requestBodyObject instanceof MultipartFormDataObject) {
461: $contentType = $requestBodyObject->getContentType();
462: $requestBody = $requestBodyObject;
463: } elseif ($requestBodyObject instanceof MultipartDataObject) {
464: $multipart = $requestBodyObject->toMultipartFormDataObject();
465: $contentType = $multipart->getContentType();
466: $requestBody = $multipart;
467: } elseif ($requestBodyObject instanceof DataObject || is_null($requestBodyObject)) {
468: $contentType = static::MIME_APPLICATION_JSON;
469: $requestBody = $requestBodyObject ? $requestBodyObject->toJson() : '';
470: } else {
471: throw new UnexpectedValueException('Unsupported request body');
472: }
473: $requestHeaders =
474: $this->getRequestHeaders('PUT', $relativeUriPathWithRequestParameters, $contentType, $clientMetaInfo, $callContext);
475:
476: $responseBuilder = new ResponseBuilder();
477: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder, $bodyHandler) {
478: $responseBuilder->setHttpStatusCode($httpStatusCode);
479: $responseBuilder->setHeaders($headers);
480: if ($httpStatusCode >= 400) {
481: $responseBuilder->appendBody($data);
482: } else {
483: call_user_func($bodyHandler, $data, $headers);
484: }
485: };
486:
487: $this->connection->put(
488: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
489: $requestHeaders,
490: $requestBody,
491: $responseHandler
492: );
493: $connectionResponse = $responseBuilder->getResponse();
494: $this->updateCallContext($connectionResponse, $callContext);
495: $httpStatusCode = $connectionResponse->getHttpStatusCode();
496: if ($httpStatusCode >= 400) {
497: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
498: throw new ErrorResponseException($httpStatusCode, $response);
499: }
500: }
501:
502: /**
503: * @param ConnectionResponse $response
504: * @param CallContext $callContext
505: */
506: protected function updateCallContext(ConnectionResponse $response, CallContext $callContext = null)
507: {
508: if ($callContext) {
509: $callContext->setIdempotenceRequestTimestamp(
510: $response->getHeaderValue('X-GCS-Idempotence-Request-Timestamp')
511: );
512: }
513: }
514:
515: /**
516: * @param $relativeUriPath
517: * @param RequestObject|null $requestParameters
518: * @return string
519: * @throws Exception
520: */
521: protected function getRequestUri($relativeUriPath, RequestObject $requestParameters = null)
522: {
523: return
524: $this->apiEndpoint .
525: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
526: }
527:
528: /**
529: * @param string $httpMethod
530: * @param string $relativeUriPathWithRequestParameters
531: * @param string $contentType
532: * @param string $clientMetaInfo
533: * @param CallContext $callContext
534: * @return string[]
535: */
536: protected function getRequestHeaders(
537: $httpMethod,
538: $relativeUriPathWithRequestParameters,
539: $contentType,
540: $clientMetaInfo = '',
541: CallContext $callContext = null
542: ) {
543: $rfc2616Date = self::getRfc161Date();
544: $requestHeaders = array();
545: $requestHeaders['Content-Type'] = $contentType;
546: $requestHeaders['Date'] = $rfc2616Date;
547: if ($clientMetaInfo) {
548: $requestHeaders['X-GCS-ClientMetaInfo'] = $clientMetaInfo;
549: }
550: $requestHeaders['X-GCS-ServerMetaInfo'] = $this->metadataProvider->getServerMetaInfoValue();
551: if ($callContext && strlen($callContext->getIdempotenceKey()) > 0) {
552: $requestHeaders['X-GCS-Idempotence-Key'] = $callContext->getIdempotenceKey();
553: }
554: $requestHeaders['Authorization'] = $this->authenticator->getAuthorization($httpMethod, $relativeUriPathWithRequestParameters, $requestHeaders);
555: return $requestHeaders;
556: }
557:
558: /**
559: * @return string
560: */
561: protected static function getRfc161Date()
562: {
563: return gmdate('D, d M Y H:i:s T');
564: }
565:
566: /**
567: * @param $relativeUriPath
568: * @param RequestObject|null $requestParameters
569: * @return string
570: */
571: protected function getRelativeUriPathWithRequestParameters(
572: $relativeUriPath,
573: RequestObject $requestParameters = null
574: ) {
575: if (is_null($requestParameters)) {
576: return $relativeUriPath;
577: }
578: $requestParameterObjectVars = get_object_vars($requestParameters);
579: if (count($requestParameterObjectVars) == 0) {
580: return $relativeUriPath;
581: }
582: $httpQuery = http_build_query($requestParameterObjectVars);
583: // remove [0], [1] etc that are added if properties are arrays
584: $httpQuery = preg_replace('/%5B[0-9]+%5D/simU', '', $httpQuery);
585: return $relativeUriPath . '?' . $httpQuery;
586: }
587:
588: /** @return ResponseFactory */
589: protected function getResponseFactory()
590: {
591: if (is_null($this->responseFactory)) {
592: $this->responseFactory = new ResponseFactory();
593: }
594: return $this->responseFactory;
595: }
596: }
597: