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