diff --git a/.gitignore b/.gitignore index c9dec9a..83c649a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/docs/sample/code/test.php /vendor/ .php_cs.cache composer.lock diff --git a/docs/sample/code/index.php b/docs/sample/code/index.php index 59e6356..81f147f 100644 --- a/docs/sample/code/index.php +++ b/docs/sample/code/index.php @@ -34,6 +34,7 @@ $config = $configBuilder->create(); $gateway = new Gateway($config); +$serviceFactory = $gateway->getServiceFactory(); // Optional: if you need to register the token first // ZohoOAuth::initialize($config->get()); @@ -42,36 +43,41 @@ /** CRUD Operations **/ $ticketDataObject = $gateway->getDataObjectFactory()->create('tickets', /* Entity values */); +$threadDataObject = $gateway->getDataObjectFactory()->create('threads', /* Entity values */); try { - $ticketDataObject = $gateway->getOperationPool()->getCreateOperation('tickets')->create($ticketDataObject); + $ticketDataObject = $serviceFactory->createOperation('tickets')->create($ticketDataObject); + $threadDataObject = $serviceFactory->createOperation('threads', 'tickets/{ticket_id}/threads')->create($threadDataObject, ['ticket_id' => $ticketDataObject->getEntityId()]); } catch (CouldNotSaveException $e) { // Handle the exception... } try { - $ticketDataObject = $gateway->getOperationPool()->getReadOperation('tickets')->get(1234); + $ticketDataObject = $serviceFactory->readOperation('tickets')->get([1234]); + $threadDataObject = $serviceFactory->readOperation('threads', 'tickets/{ticket_id}/threads/{thread_id}')->get(['ticket_id' => 1234, 'thread_id' => 1234]); } catch (CouldNotReadException $e) { // Handle the exception... } try { $criteriaBuilder = new ListCriteriaBuilder(); - // $criteriaBuilder->setFields()->setFilters()... - $ticketList = $gateway->getOperationPool()->getListOperation('tickets')->getList($criteriaBuilder->create()); - $ticketList = $gateway->getOperationPool()->getListOperation('tickets')->getByIds([1,2,3]); + // $criteriaBuilder->setFields(...)->setFilters(...)... + $ticketList = $serviceFactory->listOperation('tickets')->getList($criteriaBuilder->create()); + $threadList = $serviceFactory->listOperation('threads', 'tickets/{ticket_id}/threads')->getList($criteriaBuilder->create(), ['ticket_id' => 1234]); } catch (CouldNotReadException $e) { // Handle the exception... } try { - $ticketDataObject = $gateway->getOperationPool()->getUpdateOperation('tickets')->update($ticketDataObject); + $ticketDataObject = $serviceFactory->updateOperation('tickets')->update($ticketDataObject); + $ticketDataObject = $serviceFactory->updateOperation('threads', 'tickets/{ticket_id}/threads/{thread_id}')->update($threadDataObject, ['ticket_id' => 1234, 'thread_id' => $threadDataObject->getEntityId()]); } catch (CouldNotSaveException $e) { // Handle the exception... } try { - $gateway->getOperationPool()->getDeleteOperation('tickets', ['resolution'])->delete(1234); + $serviceFactory->deleteOperation('tickets', null, ['resolution'])->delete([1234]); + $serviceFactory->deleteOperation('threads', 'tickets/{ticket_id}/threads/{thread_id}')->delete(['ticket_id' => 1234, 'thread_id' => 1234]); } catch (CouldNotDeleteException $e) { // Handle the exception... } diff --git a/docs/sample/code/zcrm_oauthtokens.txt b/docs/sample/code/zcrm_oauthtokens.txt new file mode 100644 index 0000000..2e59250 Binary files /dev/null and b/docs/sample/code/zcrm_oauthtokens.txt differ diff --git a/docs/sample/code/zoho_oauth.log b/docs/sample/code/zoho_oauth.log new file mode 100644 index 0000000..297e733 --- /dev/null +++ b/docs/sample/code/zoho_oauth.log @@ -0,0 +1 @@ +2021-03-24 09:14:01 INFO: Access Token has expired. Hence refreshing. diff --git a/src/Client/RequestBuilder.php b/src/Client/RequestBuilder.php index 85c3a65..366b438 100644 --- a/src/Client/RequestBuilder.php +++ b/src/Client/RequestBuilder.php @@ -12,7 +12,11 @@ use Zoho\Desk\Exception\Exception; use Zoho\Desk\Exception\InvalidArgumentException; use Zoho\Desk\OAuth\ClientInterface; + +use function array_keys; +use function array_map; use function array_merge; +use function array_values; use function curl_init; use function curl_setopt; use function http_build_query; @@ -20,6 +24,8 @@ use function is_numeric; use function json_encode; use function sprintf; +use function str_replace; + use const CURLOPT_CUSTOMREQUEST; use const CURLOPT_HEADER; use const CURLOPT_HTTPHEADER; @@ -35,7 +41,7 @@ final class RequestBuilder public const HTTP_PATCH = 'PATCH'; public const HTTP_DELETE = 'DELETE'; - private const MANDATORY_FIELDS = ['entityType', 'method']; + private const MANDATORY_FIELDS = ['path', 'method']; private ClientInterface $client; @@ -61,9 +67,23 @@ public function __construct(ClientInterface $client, array $mandatoryData = []) $this->data = []; } + /** + * @deprecated + */ public function setEntityType(string $entityType): self { - $this->data['entityType'] = $entityType; + $this->data['path'] = $entityType; + + return $this; + } + + public function setPath(string $path, array $bind = []): self + { + $search = array_map(static function (string $variable): string { + return '{' . $variable . '}'; + }, array_keys($bind)); + + $this->data['path'] = str_replace($search, array_values($bind), $path); return $this; } @@ -153,7 +173,7 @@ private function buildUrl(): string 'https://%s/%s/%s', $this->client->getApiBaseUrl(), $this->client->getApiVersion(), - $this->data['entityType'] + $this->data['path'] ); if (isset($this->data['arguments']) && is_array($this->data['arguments'])) { diff --git a/src/Gateway.php b/src/Gateway.php index fbced11..827eba2 100644 --- a/src/Gateway.php +++ b/src/Gateway.php @@ -12,26 +12,30 @@ use Zoho\Desk\Model\DataObjectFactory; use Zoho\Desk\Model\OperationPool; use Zoho\Desk\OAuth\Client; +use Zoho\Desk\Service\ServiceFactory; /** * @api */ final class Gateway { + private Client $client; + + private RequestBuilder $requestBuilder; + private DataObjectFactory $dataObjectFactory; private OperationPool $operationPool; - private Client $client; - - private RequestBuilder $requestBuilder; + private ServiceFactory $serviceFactory; public function __construct(ConfigProviderInterface $configProvider, array $registeredEntityTypes = []) { - $this->dataObjectFactory = new DataObjectFactory($registeredEntityTypes); $this->client = new Client($configProvider); $this->requestBuilder = new RequestBuilder($this->client); + $this->dataObjectFactory = new DataObjectFactory($registeredEntityTypes); $this->operationPool = new OperationPool($this->requestBuilder, $this->dataObjectFactory); + $this->serviceFactory = new ServiceFactory($this->requestBuilder, $this->dataObjectFactory); } public function getDataObjectFactory(): DataObjectFactory @@ -39,11 +43,20 @@ public function getDataObjectFactory(): DataObjectFactory return $this->dataObjectFactory; } + /** + * @deprecated + * @see Gateway::getServiceFactory + */ public function getOperationPool(): OperationPool { return $this->operationPool; } + public function getServiceFactory(): ServiceFactory + { + return $this->serviceFactory; + } + public function getClient(): Client { return $this->client; diff --git a/src/Model/Operation/CreateOperation.php b/src/Model/Operation/CreateOperation.php index 132efc9..f5cbbaa 100644 --- a/src/Model/Operation/CreateOperation.php +++ b/src/Model/Operation/CreateOperation.php @@ -16,6 +16,10 @@ use Zoho\Desk\Model\DataObjectFactory; use Zoho\Desk\Model\DataObjectInterface; +/** + * @deprecated + * @see \Zoho\Desk\Service\CreateOperation + */ final class CreateOperation implements CreateOperationInterface { private RequestBuilder $requestBuilder; diff --git a/src/Model/Operation/CreateOperationInterface.php b/src/Model/Operation/CreateOperationInterface.php index 5bc093b..cf3aa96 100644 --- a/src/Model/Operation/CreateOperationInterface.php +++ b/src/Model/Operation/CreateOperationInterface.php @@ -12,6 +12,8 @@ /** * @api + * @deprecated + * @see \Zoho\Desk\Service\CreateOperationInterface */ interface CreateOperationInterface { diff --git a/src/Model/Operation/DeleteOperation.php b/src/Model/Operation/DeleteOperation.php index 37100fe..e5ea0c5 100644 --- a/src/Model/Operation/DeleteOperation.php +++ b/src/Model/Operation/DeleteOperation.php @@ -15,6 +15,10 @@ use function array_merge; use function sprintf; +/** + * @deprecated + * @see \Zoho\Desk\Service\DeleteOperation + */ final class DeleteOperation implements DeleteOperationInterface { private RequestBuilder $requestBuilder; diff --git a/src/Model/Operation/DeleteOperationInterface.php b/src/Model/Operation/DeleteOperationInterface.php index 509fe6a..81adb89 100644 --- a/src/Model/Operation/DeleteOperationInterface.php +++ b/src/Model/Operation/DeleteOperationInterface.php @@ -11,6 +11,8 @@ /** * @api + * @deprecated + * @see \Zoho\Desk\Service\DeleteOperationInterface */ interface DeleteOperationInterface { diff --git a/src/Model/Operation/ListOperation.php b/src/Model/Operation/ListOperation.php index 7b1f7d9..8520b6c 100644 --- a/src/Model/Operation/ListOperation.php +++ b/src/Model/Operation/ListOperation.php @@ -20,6 +20,10 @@ use function implode; use function is_array; +/** + * @deprecated + * @see \Zoho\Desk\Service\ListOperation + */ final class ListOperation implements ListOperationInterface { private RequestBuilder $requestBuilder; diff --git a/src/Model/Operation/ListOperationInterface.php b/src/Model/Operation/ListOperationInterface.php index 641c7b4..e258cf2 100644 --- a/src/Model/Operation/ListOperationInterface.php +++ b/src/Model/Operation/ListOperationInterface.php @@ -13,6 +13,8 @@ /** * @api + * @deprecated + * @see \Zoho\Desk\Service\ListOperationInterface */ interface ListOperationInterface { diff --git a/src/Model/Operation/ReadOperation.php b/src/Model/Operation/ReadOperation.php index 87a41c7..10deb49 100644 --- a/src/Model/Operation/ReadOperation.php +++ b/src/Model/Operation/ReadOperation.php @@ -17,6 +17,10 @@ use Zoho\Desk\Model\DataObjectInterface; use function array_merge; +/** + * @deprecated + * @see \Zoho\Desk\Service\ReadOperation + */ final class ReadOperation implements ReadOperationInterface { private RequestBuilder $requestBuilder; diff --git a/src/Model/Operation/ReadOperationInterface.php b/src/Model/Operation/ReadOperationInterface.php index db60fc4..f8d319f 100644 --- a/src/Model/Operation/ReadOperationInterface.php +++ b/src/Model/Operation/ReadOperationInterface.php @@ -12,6 +12,8 @@ /** * @api + * @deprecated + * @see \Zoho\Desk\Service\ReadOperationInterface */ interface ReadOperationInterface { diff --git a/src/Model/Operation/UpdateOperation.php b/src/Model/Operation/UpdateOperation.php index f0681e4..b388c49 100644 --- a/src/Model/Operation/UpdateOperation.php +++ b/src/Model/Operation/UpdateOperation.php @@ -18,6 +18,10 @@ use function array_merge; use function sprintf; +/** + * @deprecated + * @see \Zoho\Desk\Service\UpdateOperation + */ final class UpdateOperation implements UpdateOperationInterface { private RequestBuilder $requestBuilder; diff --git a/src/Model/Operation/UpdateOperationInterface.php b/src/Model/Operation/UpdateOperationInterface.php index 6ad7f18..31217aa 100644 --- a/src/Model/Operation/UpdateOperationInterface.php +++ b/src/Model/Operation/UpdateOperationInterface.php @@ -12,6 +12,8 @@ /** * @api + * @deprecated + * @see \Zoho\Desk\Service\UpdateOperationInterface */ interface UpdateOperationInterface { diff --git a/src/Model/OperationPool.php b/src/Model/OperationPool.php index 2545f65..e108efe 100644 --- a/src/Model/OperationPool.php +++ b/src/Model/OperationPool.php @@ -18,9 +18,14 @@ use Zoho\Desk\Model\Operation\ReadOperationInterface; use Zoho\Desk\Model\Operation\UpdateOperation; use Zoho\Desk\Model\Operation\UpdateOperationInterface; + use function implode; use function md5; +/** + * @deprecated + * @see \Zoho\Desk\Service\ServiceFactory + */ final class OperationPool { private RequestBuilder $requestBuilder; diff --git a/src/Service/CreateOperation.php b/src/Service/CreateOperation.php new file mode 100644 index 0000000..81fca09 --- /dev/null +++ b/src/Service/CreateOperation.php @@ -0,0 +1,76 @@ +requestBuilder = $requestBuilder; + $this->dataObjectFactory = $dataObjectFactory; + $this->entityType = $entityType; + $this->path = $path; + $this->arguments = $arguments; + } + + public function create(DataObjectInterface $dataObject, array $bind = []): DataObjectInterface + { + try { + return $this->dataObjectFactory->create($this->entityType, $this->saveEntity($dataObject)->getResult()); + } catch (InvalidArgumentException $e) { + throw new CouldNotSaveException($e->getMessage(), $e->getCode(), $e); + } catch (InvalidRequestException $e) { + throw new CouldNotSaveException($e->getMessage(), $e->getCode(), $e); + } catch (Exception $e) { + throw new CouldNotSaveException('Could not create the entity.', $e->getCode(), $e); + } + } + + /** + * @throws Exception + * @throws InvalidArgumentException + * @throws InvalidRequestException + */ + private function saveEntity(DataObjectInterface $dataObject, array $bind = []): ResponseInterface + { + return $this->requestBuilder + ->setPath($this->path ?? $this->entityType, $bind) + ->setMethod(RequestBuilder::HTTP_POST) + ->setArguments($this->arguments) + ->setFields($dataObject->toArray()) + ->create() + ->execute(); + } +} diff --git a/src/Service/CreateOperationInterface.php b/src/Service/CreateOperationInterface.php new file mode 100644 index 0000000..adcf8f9 --- /dev/null +++ b/src/Service/CreateOperationInterface.php @@ -0,0 +1,22 @@ +requestBuilder = $requestBuilder; + $this->entityType = $entityType; + $this->path = $path; + $this->arguments = $arguments; + } + + public function delete(array $bind): void + { + try { + $this->requestBuilder + ->setPath($this->path ?? $this->entityType, $bind) + ->setMethod(RequestBuilder::HTTP_DELETE) + ->setArguments($this->path ? $this->arguments : array_merge([reset($bind)], $this->arguments)) + ->create() + ->execute(); + } catch (InvalidArgumentException $e) { + throw new CouldNotDeleteException($e->getMessage(), $e->getCode(), $e); + } catch (InvalidRequestException $e) { + throw new CouldNotDeleteException($e->getMessage(), $e->getCode(), $e); + } catch (Exception $e) { + $flatten = ''; + foreach ($bind as $key => $value) { + $flatten .= sprintf('%s: %s ', $key, $value); + } + throw new CouldNotDeleteException( + sprintf('Could not delete the entity with %s.', rtrim($flatten)), + $e->getCode(), + $e + ); + } + } +} diff --git a/src/Service/DeleteOperationInterface.php b/src/Service/DeleteOperationInterface.php new file mode 100644 index 0000000..efef99b --- /dev/null +++ b/src/Service/DeleteOperationInterface.php @@ -0,0 +1,21 @@ +requestBuilder = $requestBuilder; + $this->dataObjectFactory = $dataObjectFactory; + $this->entityType = $entityType; + $this->path = $path; + $this->arguments = $arguments; + } + + public function getList(ListCriteriaInterface $listCriteria, array $bind = []): array + { + $arguments = $listCriteria->getFilters() ? array_merge(['search'], $this->arguments) : $this->arguments; + + try { + $response = $this->fetchResult($arguments, $listCriteria->getQueryParams(), $bind); + } catch (InvalidArgumentException $e) { + throw new CouldNotReadException($e->getMessage(), $e->getCode(), $e); + } catch (InvalidRequestException $e) { + throw new CouldNotReadException($e->getMessage(), $e->getCode(), $e); + } catch (Exception $e) { + throw new CouldNotReadException('Could not fetch the entities.', $e->getCode(), $e); + } + + return $this->buildEntities($response); + } + + /** + * @return DataObjectInterface[] + */ + private function buildEntities(ResponseInterface $response): array + { + $entities = []; + $result = $response->getResult(); + if (isset($result['data']) && is_array($result['data'])) { + foreach ($result['data'] as $entity) { + $entities[] = $this->dataObjectFactory->create($this->entityType, $entity); + } + } + + return $entities; + } + + /** + * @throws Exception + * @throws InvalidArgumentException + * @throws InvalidRequestException + */ + private function fetchResult(array $arguments, array $params = [], array $bind = []): ResponseInterface + { + return $this->requestBuilder + ->setPath($this->path ?? $this->entityType, $bind) + ->setMethod(RequestBuilder::HTTP_GET) + ->setArguments($arguments) + ->setQueryParameters($params) + ->create() + ->execute(); + } +} diff --git a/src/Service/ListOperationInterface.php b/src/Service/ListOperationInterface.php new file mode 100644 index 0000000..04ce385 --- /dev/null +++ b/src/Service/ListOperationInterface.php @@ -0,0 +1,23 @@ +requestBuilder = $requestBuilder; + $this->dataObjectFactory = $dataObjectFactory; + $this->entityType = $entityType; + $this->path = $path; + $this->arguments = $arguments; + } + + public function get(array $bind, array $query = []): DataObjectInterface + { + try { + return $this->dataObjectFactory->create($this->entityType, $this->fetchEntity($bind, $query)->getResult()); + } catch (InvalidArgumentException $e) { + throw new CouldNotReadException($e->getMessage(), $e->getCode(), $e); + } catch (InvalidRequestException $e) { + throw new CouldNotReadException($e->getMessage(), $e->getCode(), $e); + } catch (Exception $e) { + throw new CouldNotReadException('Could not fetch the entity.', $e->getCode(), $e); + } + } + + /** + * @throws Exception + * @throws InvalidArgumentException + * @throws InvalidRequestException + */ + private function fetchEntity(array $bind = [], array $query = []): ResponseInterface + { + return $this->requestBuilder + ->setPath($this->path ?? $this->entityType, $bind) + ->setMethod(RequestBuilder::HTTP_GET) + ->setArguments($this->path ? $this->arguments : array_merge([reset($bind)], $this->arguments)) + ->setQueryParameters($query) + ->create() + ->execute(); + } +} diff --git a/src/Service/ReadOperationInterface.php b/src/Service/ReadOperationInterface.php new file mode 100644 index 0000000..c448dc6 --- /dev/null +++ b/src/Service/ReadOperationInterface.php @@ -0,0 +1,22 @@ +requestBuilder = $requestBuilder; + $this->dataObjectFactory = $dataObjectFactory; + } + + public function createOperation(string $entityType, ?string $path = null, array $arguments = []): CreateOperationInterface + { + return new CreateOperation($this->requestBuilder, $this->dataObjectFactory, $entityType, $path, $arguments); + } + + public function readOperation(string $entityType, ?string $path = null, array $arguments = []): ReadOperationInterface + { + return new ReadOperation($this->requestBuilder, $this->dataObjectFactory, $entityType, $path, $arguments); + } + + public function updateOperation(string $entityType, ?string $path = null, array $arguments = []): UpdateOperationInterface + { + return new UpdateOperation($this->requestBuilder, $this->dataObjectFactory, $entityType, $path, $arguments); + } + + public function deleteOperation(string $entityType, ?string $path = null, array $arguments = []): DeleteOperationInterface + { + return new DeleteOperation($this->requestBuilder, $entityType, $path, $arguments); + } + + public function listOperation(string $entityType, ?string $path = null, array $arguments = []): ListOperationInterface + { + return new ListOperation($this->requestBuilder, $this->dataObjectFactory, $entityType, $path, $arguments); + } +} diff --git a/src/Service/UpdateOperation.php b/src/Service/UpdateOperation.php new file mode 100644 index 0000000..eb30353 --- /dev/null +++ b/src/Service/UpdateOperation.php @@ -0,0 +1,87 @@ +requestBuilder = $requestBuilder; + $this->dataObjectFactory = $dataObjectFactory; + $this->entityType = $entityType; + $this->path = $path; + $this->arguments = $arguments; + } + + public function update(DataObjectInterface $dataObject, array $bind = []): DataObjectInterface + { + if (!$dataObject->getEntityId()) { + throw new CouldNotSaveException('Could not update an entity without ID.'); + } + + try { + return $this->dataObjectFactory->create($this->entityType, $this->saveEntity($dataObject, $bind)->getResult()); + } catch (InvalidArgumentException $e) { + throw new CouldNotSaveException($e->getMessage(), $e->getCode(), $e); + } catch (InvalidRequestException $e) { + throw new CouldNotSaveException($e->getMessage(), $e->getCode(), $e); + } catch (Exception $e) { + throw new CouldNotSaveException( + sprintf('Could not update the entity with ID "%u".', $dataObject->getEntityId()), + $e->getCode(), + $e + ); + } + } + + /** + * @throws Exception + * @throws InvalidArgumentException + * @throws InvalidRequestException + */ + private function saveEntity(DataObjectInterface $dataObject, array $bind = []): ResponseInterface + { + return $this->requestBuilder + ->setPath($this->path ?? $this->entityType, $bind) + ->setMethod(RequestBuilder::HTTP_PATCH) + ->setArguments($this->path ? $this->arguments : array_merge([reset($bind)], $this->arguments)) + ->setFields($dataObject->toArray()) + ->create() + ->execute(); + } +} diff --git a/src/Service/UpdateOperationInterface.php b/src/Service/UpdateOperationInterface.php new file mode 100644 index 0000000..bcd60b3 --- /dev/null +++ b/src/Service/UpdateOperationInterface.php @@ -0,0 +1,22 @@ +