From bbf6420e5aae7d898da8f9a797896d8672a0b5f5 Mon Sep 17 00:00:00 2001 From: Daniele Rosario Date: Tue, 10 Jan 2023 16:12:52 +0100 Subject: [PATCH 1/5] wip --- CHANGELOG.md | 4 ++++ src/Client.php | 10 +++++----- src/Modules/Records.php | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e375d3..105e4c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [WIP] +- API v4 +- Support for getting records via external ids + ## [5.2.0] - Added support for illuminate collections v9 - Added tests for php 8.1 diff --git a/src/Client.php b/src/Client.php index 5eb5819..8655ece 100644 --- a/src/Client.php +++ b/src/Client.php @@ -14,7 +14,7 @@ class Client { - protected const ZOHOCRM_API_URL_PATH = "/crm/v2/"; + protected const ZOHOCRM_API_URL_PATH = "/crm/v4/"; protected const ZOHOCRM_API_PRODUCION_PARTIAL_HOST = "https://www.zohoapis"; protected const ZOHOCRM_API_DEVELOPER_PARTIAL_HOST = "https://developer.zoho"; protected const ZOHOCRM_API_SANDBOX_PARTIAL_HOST = "https://crmsandbox.zoho"; @@ -132,8 +132,7 @@ public function call(string $uri, string $method, array $data = []): ResponseInt ], $data); $options['headers'] = array_merge($data['headers'] ?? [], [ - 'Authorization' => - 'Zoho-oauthtoken ' . $this->oAuthClient->getAccessToken(), + 'Authorization' => 'Zoho-oauthtoken ' . $this->oAuthClient->getAccessToken(), ]); try { @@ -223,13 +222,14 @@ public function getRegion(): string return $this->oAuthClient->getRegion(); } - public function get(string $url, string $id = null, array $params = []) + public function get(string $url, string $id = null, array $params = [], array $options = []) { if ($id !== null) { $url .= '/' . $id; } - $result = $this->call($url, 'GET', ['query' => $params]); + $options['query'] = array_merge($options['query'] ?? [], $params); + $result = $this->call($url, 'GET', $options); return $this->processResult($result); } diff --git a/src/Modules/Records.php b/src/Modules/Records.php index 138bd16..29f0b50 100644 --- a/src/Modules/Records.php +++ b/src/Modules/Records.php @@ -3,6 +3,7 @@ namespace Webleit\ZohoCrmApi\Modules; use Webleit\ZohoCrmApi\Client; +use Webleit\ZohoCrmApi\Models\Model; use Webleit\ZohoCrmApi\Models\Record; use Webleit\ZohoCrmApi\RecordCollection; @@ -29,6 +30,24 @@ public function __construct(Client $client, $module = '') $this->module = $module; } + public function get(string $id, array $params = [], ?string $externalField = null): Model + { + $options = []; + if ($externalField !== null) { + $options['headers'] = [ + "X-EXTERNAL" => $this->getModuleName() . "." . $externalField + ]; + } + + $item = $this->client->get($this->getUrl(), $id, $params, $options); + + $items = $item[$this->getResourceKey()] ?? []; + + $data = array_shift($items); + + return $this->make($data ?: []); + } + public function searchRaw(string $criteria): RecordCollection { return $this->search($criteria, 'criteria'); From b4706a4f734260fb49271d2b1ecd5ba6be9f9e41 Mon Sep 17 00:00:00 2001 From: Daniele Rosario Date: Mon, 30 Jan 2023 18:11:21 +0100 Subject: [PATCH 2/5] wip --- composer.json | 13 +-- phpstan.neon | 6 ++ src/Client.php | 116 ++++++++++-------------- src/Contracts/Model.php | 3 + src/Exception/ApiError.php | 19 +++- src/Mixins/HasInflector.php | 6 +- src/Mixins/ProvidesModules.php | 4 +- src/Models/Model.php | 42 +++++---- src/Models/Record.php | 28 +++++- src/Models/Settings/Module.php | 14 +++ src/Modules/Leads.php | 13 ++- src/Modules/Module.php | 149 ++++++++++++++++++++++++------- src/Modules/Org.php | 9 +- src/Modules/Records.php | 51 ++++++++--- src/Modules/Settings.php | 11 +-- src/Modules/Settings/Modules.php | 59 ++++-------- src/Modules/Users.php | 10 ++- src/RecordCollection.php | 7 +- src/Request/ListHeaders.php | 22 ++++- src/Request/ListParameters.php | 23 ++++- src/Request/Pagination.php | 13 +-- src/ZohoCrm.php | 45 ++++------ tests/ApiTest.php | 2 +- 23 files changed, 424 insertions(+), 241 deletions(-) create mode 100644 phpstan.neon diff --git a/composer.json b/composer.json index f3140a5..82bbb34 100644 --- a/composer.json +++ b/composer.json @@ -10,18 +10,19 @@ } ], "require": { - "php" : "^7.3 || ^8.0", + "php": "^8.0", + "ext-json": "*", "guzzlehttp/guzzle": "^6.3 || ^7.0", "illuminate/collections": "^8.0 || ^9.0", "doctrine/inflector": "^2.0", - "weble/zohoclient": "^4.2", - "ext-json": "*" + "weble/zohoclient": "dev-feature/v5" }, "require-dev": { - "phpunit/phpunit": "^8.0 || ^9.0", - "nunomaduro/collision": "^5.0 || ^6.0", + "phpunit/phpunit": "^9.1", + "nunomaduro/collision": "^6.0", "cache/filesystem-adapter": "^1.0", - "caseyamcl/guzzle_retry_middleware": "^2.6" + "caseyamcl/guzzle_retry_middleware": "^2.6", + "phpstan/phpstan": "^1.9" }, "autoload": { "psr-4": { diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..21f5a78 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: 6 + paths: + - src + - tests + checkGenericClassInNonGenericObjectType: false diff --git a/src/Client.php b/src/Client.php index 8655ece..a9b7c10 100644 --- a/src/Client.php +++ b/src/Client.php @@ -2,6 +2,7 @@ namespace Webleit\ZohoCrmApi; +use Closure; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\ClientException; use Psr\Http\Message\ResponseInterface; @@ -21,27 +22,13 @@ class Client public const SUCCESS_CODE = 'SUCCESS'; - /** - * @var bool - */ - protected $retriedRefresh = false; - - /** - * @var \GuzzleHttp\Client - */ - protected $client; - - /** - * @var OAuthClient - */ - protected $oAuthClient; + protected bool $retriedRefresh = false; + protected ClientInterface $client; + protected OAuthClient $oAuthClient; - /** - * @var string - */ - protected $mode; + protected string $mode; - public function __construct(OAuthClient $oAuthClient, ClientInterface $client = null) + public function __construct(OAuthClient $oAuthClient, ?ClientInterface $client = null, string $mode = Mode::PRODUCTION) { if (! $client) { $client = new \GuzzleHttp\Client(); @@ -50,15 +37,21 @@ public function __construct(OAuthClient $oAuthClient, ClientInterface $client = $this->client = $client; $this->oAuthClient = $oAuthClient; - $this->setMode(Mode::PRODUCTION); + $this->setMode($mode); } - public function __call($name, $arguments) + /** + * @param string $name + * @param array $arguments + */ + public function __call(string $name, array $arguments): mixed { - return call_user_func_array([ + /** @var Closure $callback */ + $callback = [ $this->oAuthClient, $name, - ], $arguments); + ]; + return call_user_func_array($callback, $arguments); } public function getOAuthClient(): OAuthClient @@ -82,27 +75,11 @@ public function setRegion(string $region): self /** * @param string $uri - * @param array|ListParameters $params - * @param array|ListHeaders $headers - * @return array - * @throws ApiError - * @throws Exception\AuthFailed - * @throws Exception\DuplicateData - * @throws Exception\InvalidData - * @throws Exception\InvalidDataFormat - * @throws Exception\InvalidDataType - * @throws Exception\InvalidModule - * @throws Exception\InvalidUrlPattern - * @throws Exception\LimitExceeded - * @throws Exception\MandatoryDataNotFound - * @throws Exception\MethodNotAllowed - * @throws Exception\OAuthScopeMismatch - * @throws Exception\RequestEntityTooLarge - * @throws Exception\TooManyRequests - * @throws Exception\Unauthorized - * @throws Exception\UnsupportedMediaType + * @param array|ListParameters $params + * @param array|ListHeaders $headers + * @return array */ - public function getList(string $uri, $params = [], $headers = []): array + public function getList(string $uri, array|ListParameters $params = [], array|ListHeaders $headers = []): array { if (! $params instanceof ListParameters) { $params = new ListParameters($params); @@ -123,6 +100,9 @@ public function getList(string $uri, $params = [], $headers = []): array return $data ?? []; } + /** + * @param array $data + */ public function call(string $uri, string $method, array $data = []): ResponseInterface { $options = array_merge([ @@ -152,10 +132,6 @@ public function call(string $uri, string $method, array $data = []): ResponseInt $response = $e->getResponse(); - if (! $response) { - throw $e; - } - ApiError::throwFromResponse($response); return $response; @@ -222,7 +198,12 @@ public function getRegion(): string return $this->oAuthClient->getRegion(); } - public function get(string $url, string $id = null, array $params = [], array $options = []) + /** + * @param array $params + * @param array $options + * @return array|string + */ + public function get(string $url, string $id = null, array $params = [], array $options = []): array|string { if ($id !== null) { $url .= '/' . $id; @@ -234,7 +215,12 @@ public function get(string $url, string $id = null, array $params = [], array $o return $this->processResult($result); } - public function post(string $url, array $params = [], array $queryParams = []) + /** + * @param array $params + * @param array $queryParams + * @return array|string + */ + public function post(string $url, array $params = [], array $queryParams = []): array|string { return $this->processResult($this->call($url, 'POST', [ 'query' => $queryParams, @@ -242,7 +228,12 @@ public function post(string $url, array $params = [], array $queryParams = []) ])); } - public function put(string $url, array $params = [], array $queryParams = []) + /** + * @param array $params + * @param array $queryParams + * @return array|string + */ + public function put(string $url, array $params = [], array $queryParams = []): array|string { return $this->processResult($this->call($url, 'PUT', [ 'query' => $queryParams, @@ -250,35 +241,22 @@ public function put(string $url, array $params = [], array $queryParams = []) ])); } - public function delete(string $url, $id) + /** + * @return array|string + */ + public function delete(string $url, string $id): array|string { return $this->processResult($this->call($url . '/' . $id, 'DELETE')); } - public function getHttpClient(): \GuzzleHttp\Client + public function getHttpClient(): ClientInterface { return $this->client; } /** * @param ResponseInterface $response - * @return array|string - * @throws ApiError - * @throws Exception\AuthFailed - * @throws Exception\DuplicateData - * @throws Exception\InvalidData - * @throws Exception\InvalidDataFormat - * @throws Exception\InvalidDataType - * @throws Exception\InvalidModule - * @throws Exception\InvalidUrlPattern - * @throws Exception\LimitExceeded - * @throws Exception\MandatoryDataNotFound - * @throws Exception\MethodNotAllowed - * @throws Exception\OAuthScopeMismatch - * @throws Exception\RequestEntityTooLarge - * @throws Exception\TooManyRequests - * @throws Exception\Unauthorized - * @throws Exception\UnsupportedMediaType + * @return array|string */ public function processResult(ResponseInterface $response) { diff --git a/src/Contracts/Model.php b/src/Contracts/Model.php index 3e62a15..479cc58 100644 --- a/src/Contracts/Model.php +++ b/src/Contracts/Model.php @@ -10,6 +10,9 @@ interface Model extends Arrayable, Jsonable, JsonSerializable { public function getModule(): Module; + /** + * @return array + */ public function getData(): array; public function isNew(): bool; diff --git a/src/Exception/ApiError.php b/src/Exception/ApiError.php index a97a0db..86be83c 100644 --- a/src/Exception/ApiError.php +++ b/src/Exception/ApiError.php @@ -2,6 +2,7 @@ namespace Webleit\ZohoCrmApi\Exception; +use Illuminate\Support\Collection; use Psr\Http\Message\ResponseInterface; /** @@ -10,8 +11,7 @@ */ class ApiError extends \Exception { - /** @var ResponseInterface */ - protected $response; + protected ResponseInterface $response; // Error Codes protected const INVALID_MODULE = 'INVALID_MODULE'; @@ -28,8 +28,14 @@ class ApiError extends \Exception protected const TOO_MANY_REQUESTS = 'TOO_MANY_REQUESTS'; protected const INVALID_TOKEN = 'INVALID_TOKEN'; - protected $details = []; + /** + * @var array + */ + protected array $details = []; + /** + * @param array $details + */ public function __construct(ResponseInterface $response, array $details = []) { parent::__construct($response->getReasonPhrase(), $response->getStatusCode()); @@ -38,6 +44,9 @@ public function __construct(ResponseInterface $response, array $details = []) $this->details = $details; } + /** + * @return array + */ public function details(): array { return $this->details; @@ -123,6 +132,9 @@ public static function throwFromResponse(ResponseInterface $response): void } } + /** + * @return array + */ protected static function getErrorCodeAndDetailsFromResponse(ResponseInterface $response): array { try { @@ -156,6 +168,7 @@ protected static function getErrorCodeAndDetailsFromResponse(ResponseInterface $ ]; } + /** @phpstan-ignore-next-line $body */ $body = collect($body); return [ diff --git a/src/Mixins/HasInflector.php b/src/Mixins/HasInflector.php index 0afc35a..d102a3f 100644 --- a/src/Mixins/HasInflector.php +++ b/src/Mixins/HasInflector.php @@ -8,10 +8,8 @@ trait HasInflector { - /** - * @var \Doctrine\Inflector\Inflector - */ - protected $inflector = null; + + protected ?Inflector $inflector = null; public function inflector(): Inflector { diff --git a/src/Mixins/ProvidesModules.php b/src/Mixins/ProvidesModules.php index c3db24a..f560957 100644 --- a/src/Mixins/ProvidesModules.php +++ b/src/Mixins/ProvidesModules.php @@ -12,7 +12,9 @@ public function createModule(string $name): ?Module if ($this->getAvailableModules()->has($name)) { $class = $this->getAvailableModules()->get($name); - return new $class($this->client); + /** @var Module $module */ + $module = new $class($this->client); + return $module; } return null; diff --git a/src/Models/Model.php b/src/Models/Model.php index 61448de..4186a05 100644 --- a/src/Models/Model.php +++ b/src/Models/Model.php @@ -13,16 +13,13 @@ abstract class Model implements \Webleit\ZohoCrmApi\Contracts\Model { use HasInflector; - /** - * @var array - */ - protected $data = []; + /** @var array */ + protected array $data = []; + protected Module $module; /** - * @var Module + * @param array $data */ - protected $module; - public function __construct(array $data, Module $module) { $this->data = $data; @@ -34,51 +31,64 @@ public function getModule(): Module return $this->module; } - public function __get($name) + /** @phpstan-ignore-next-line */ + public function __get(string $name) { if (isset($this->data[$name])) { return $this->data[$name]; } } - public function __set($name, $value) + public function __set(string $name, mixed $value): void { $this->data[$name] = $value; } - - function __isset($name) + + function __isset(string $name): bool { return isset($this->data[$name]); } - public function __call($name, $arguments) + /** @phpstan-ignore-next-line */ + public function __call(string $name, array $arguments) { // add "id" as a parameter array_unshift($arguments, $this->getId()); if (method_exists($this->module, $name)) { - return call_user_func_array([ + /** @var callable $callback */ + $callback = [ $this->module, $name, - ], $arguments); + ]; + return call_user_func_array($callback, $arguments); } } + /** + * @return array + */ public function getData(): array { return $this->data; } - public function toArray() + /** + * @return array + */ + public function toArray(): array { return $this->getData(); } - public function toJson($options = 0): string + public function toJson($options = 0): string|false { return json_encode($this->toArray(), $options); } + /** + * @return array + */ public function jsonSerialize(): array { return $this->toArray(); diff --git a/src/Models/Record.php b/src/Models/Record.php index 5a1db71..490e2dd 100644 --- a/src/Models/Record.php +++ b/src/Models/Record.php @@ -2,20 +2,42 @@ namespace Webleit\ZohoCrmApi\Models; +use Webleit\ZohoCrmApi\Contracts\Module; +use Webleit\ZohoCrmApi\Modules\Records; + class Record extends Model { - public function uploadPhoto(string $fileName, $fileContents): bool + public function getModule(): Records + { + /** @var Records $module */ + $module = $this->module; + return $module; + } + + public function uploadPhoto(string $fileName, string $fileContents): bool { + if (!$this->getId()) { + return false; + } + return $this->getModule()->uploadPhoto($this->getId(), $fileName, $fileContents); } - public function uploadAttachment(string $fileName, $fileContents): bool + public function uploadAttachment(string $fileName, string $fileContents): bool { + if (!$this->getId()) { + return false; + } + return $this->getModule()->uploadAttachment($this->getId(), $fileName, $fileContents); } - public function downloadAttachment(string $attachmentId, $resource): void + public function downloadAttachment(string $attachmentId, string $resource): void { + if (!$this->getId()) { + return; + } + $this->getModule()->downloadAttachment($this->getId(), $attachmentId, $resource); } } diff --git a/src/Models/Settings/Module.php b/src/Models/Settings/Module.php index 9af5cf8..2fbff3a 100644 --- a/src/Models/Settings/Module.php +++ b/src/Models/Settings/Module.php @@ -4,9 +4,23 @@ use Illuminate\Support\Collection; use Webleit\ZohoCrmApi\Models\Model; +use Webleit\ZohoCrmApi\Models\Record; +use Webleit\ZohoCrmApi\Modules\Settings; +/** + * @property-read string $api_name + * @property-read string $module_name + */ class Module extends Model { + public function getModule(): Settings\Modules + { + /** @var Settings\Modules $module */ + $module = $this->module; + + return $module; + } + public function getFields(): Collection { return $this->getModule()->getFieldsForModule($this); diff --git a/src/Modules/Leads.php b/src/Modules/Leads.php index 81d50e0..7dff699 100644 --- a/src/Modules/Leads.php +++ b/src/Modules/Leads.php @@ -6,15 +6,17 @@ class Leads extends Records { - public function __construct(Client $client, $module = 'Leads') + public function __construct(Client $client, string $module = 'Leads') { parent::__construct($client, $module); } /** * @see https://www.zoho.com/crm/developer/docs/api/convert-lead.html + * @param array $data + * @return array */ - public function convertLead(string $leadId, array $data = []) + public function convertLead(string $leadId, array $data = []): array { return $this->doAction($leadId, 'convert', [ 'data' => [ @@ -25,7 +27,12 @@ public function convertLead(string $leadId, array $data = []) ])['data'][0] ?? []; } - public function convert(string $leadId, array $data = []) + /** + * @see https://www.zoho.com/crm/developer/docs/api/convert-lead.html + * @param array $data + * @return array + */ + public function convert(string $leadId, array $data = []): array { return $this->convertLead($leadId, $data); } diff --git a/src/Modules/Module.php b/src/Modules/Module.php index adef0fe..24fab2b 100644 --- a/src/Modules/Module.php +++ b/src/Modules/Module.php @@ -2,6 +2,7 @@ namespace Webleit\ZohoCrmApi\Modules; +use Exception; use Illuminate\Support\Collection; use Webleit\ZohoCrmApi\Client; use Webleit\ZohoCrmApi\Enums\Trigger; @@ -18,45 +19,68 @@ abstract class Module implements \Webleit\ZohoCrmApi\Contracts\Module /** * @var Client */ - protected $client; + protected Client $client; public function __construct(Client $client) { $this->client = $client; } + /** + * @param array $params + * @param array $headers + */ public function getList(array $params = [], array $headers = []): RecordCollection { + /** @var array $list */ $list = $this->client->getList($this->getUrl(), $params, $headers); $data = $list[$this->getResourceKey()] ?? null; if ($data === null) { - throw new InvalidResourceKey(json_encode($list)); + throw new InvalidResourceKey(json_encode($list) ?: ''); } - $collection = new RecordCollection($data ?? []); - $collection = $collection->mapWithKeys(function ($item) { - $item = $this->make($item); + /** @var array $data */ - return [$item->getId() => $item]; - }); + /** @var RecordCollection $collection */ + $collection = (new RecordCollection($data)) + ->mapWithKeys(function ($data) { + /** @var array $data */ + $item = $this->make($data); + + return [$item->getId() => $item]; + }); - $collection->withPagination(new Pagination($list['info'] ?? [])); + /** @var array{"per_page"?: int, "page"?: int, "count"?: int, "more_records"?: bool} $info */ + $info = $list['info'] ?? []; + return $collection->withPagination(new Pagination($info)); - return $collection; } + /** + * @param array $params + */ public function get(string $id, array $params = []): Model { $item = $this->client->get($this->getUrl(), $id, $params); + if (!is_array($item)) { + return $this->make(); + } + /** @var array> $items */ $items = $item[$this->getResourceKey()] ?? []; + /** @var array $data */ $data = array_shift($items); return $this->make($data ?: []); } + /** + * @param mixed[] $data + * @param array $params + * @param string[] $triggers + */ public function create(array $data, array $params = [], array $triggers = [ Trigger::APPROVAL, Trigger::WORKFLOW, @@ -66,14 +90,19 @@ public function create(array $data, array $params = [], array $triggers = [ return $this->createMany([$data], $params, $triggers)->first(); } - public function createMany($data, $params = [], $triggers = [ + /** + * @param array $data + * @param array $params + * @param string[] $triggers + */ + public function createMany(mixed $data, array $params = [], array $triggers = [ Trigger::APPROVAL, Trigger::WORKFLOW, Trigger::BLUEPRINT, ]): Collection { $data = [ - 'data' => (array)$data, + 'data' => (array)$data, 'trigger' => $triggers, ]; @@ -94,6 +123,11 @@ public function createMany($data, $params = [], $triggers = [ return collect($results); } + /** + * @param array $data + * @param array $params + * @param string[] $triggers + */ public function update(string $id, array $data, array $params = [], array $triggers = [ Trigger::APPROVAL, Trigger::WORKFLOW, @@ -103,16 +137,22 @@ public function update(string $id, array $data, array $params = [], array $trigg $data['id'] = $id; $data = [ - 'data' => [$data], + 'data' => [$data], 'trigger' => $triggers, ]; $data = $this->client->put($this->getUrl(), $data, $params); + $data = $data['data'] ?? []; $row = array_shift($data['data']); return $this->make($row['details']); } + /** + * @param array $data + * @param array $params + * @param string[] $triggers + */ public function updateMany(array $data, array $params = [], array $triggers = [ Trigger::APPROVAL, Trigger::WORKFLOW, @@ -120,7 +160,7 @@ public function updateMany(array $data, array $params = [], array $triggers = [ ]): Collection { $data = [ - 'data' => $data, + 'data' => $data, 'trigger' => $triggers, ]; @@ -144,6 +184,10 @@ public function delete(string $id): bool return true; } + /** + * @param array $data + * @return array + */ public function updateRelatedRecord(string $recordId, string $relationName, string $relatedRecordId, array $data = []): array { $data = array_merge($data, [ @@ -156,12 +200,27 @@ public function updateRelatedRecord(string $recordId, string $relationName, stri ], ]; - return $this->client->put($this->getUrl() . '/' . $recordId . '/' . $relationName . '/' . $relatedRecordId, $putData); + $result = $this->client->put($this->getUrl() . '/' . $recordId . '/' . $relationName . '/' . $relatedRecordId, $putData); + if (!is_array($result)) { + throw new Exception($result); + } + + return $result; } + /** + * @return array + */ public function getRelatedRecords(string $recordId, string $relationName): array { - return $this->client->get($this->getUrl() . '/' . $recordId . '/' . $relationName) ?? []; + $result = $this->client->get($this->getUrl() . '/' . $recordId . '/' . $relationName) ?: []; + + if (!is_array($result)) { + throw new Exception($result); + } + + return $result; + } public function getUrlPath(): string @@ -190,11 +249,17 @@ protected function getResourceKey(): string return strtolower($this->getName()); } + /** + * @param array $data + */ public function make(array $data = []): Model { $class = $this->getModelClassName(); - return new $class($data, $this); + /** @var Model $model */ + $model = new $class($data, $this); + + return $model; } public function getClient(): Client @@ -209,46 +274,70 @@ public function markAs(string $id, string $status, string $key = 'status'): bool return true; } + /** + * @param array $params + */ public function notes(string $id, array $params = []): RecordCollection { return $this->getRelatedResources('Notes', $id, $params); } + /** + * @param array $params + */ public function attachments(string $id, array $params = []): RecordCollection { return $this->getRelatedResources('Attachments', $id, $params); } + /** + * @param array $params + */ public function getRelatedResources(string $resource, string $id, array $params = []): RecordCollection { $data = $this->client->getList($this->getUrl() . '/' . $id . '/' . $resource, $params); + if (!is_array($data)) { + throw new \Exception($data); + } - $collection = new RecordCollection($data['data'] ?? []); - $collection = $collection->mapWithKeys(function ($item) { - $item = $this->make($item); + /** @var RecordCollection $collection */ + $collection = (new RecordCollection($data['data'] ?? [])) + ->mapWithKeys(function ($item) { + $item = $this->make($item); - return [$item->getId() => $item]; - }); + return [$item->getId() => $item]; + }); - return $collection->withPagination(new Pagination($list['info'] ?? [])); + return $collection->withPagination(new Pagination($data['info'] ?? [])); } + /** + * @param array $data + * @param array $params + * @return array + */ public function doAction(string $id, string $action, array $data = [], array $params = []): array { - return $this->client->post($this->getUrl() . '/' . $id . '/actions/' . $action, $data, $params); + $data = $this->client->post($this->getUrl() . '/' . $id . '/actions/' . $action, $data, $params); + + if (!is_array($data)) { + throw new \Exception($data); + } + + return $data; } - protected function getPropertyList(string $property, ?string $id = null, ?string $class = null, ?string $subProperty = null, ?\Webleit\ZohoCrmApi\Contracts\Module $module = null) + protected function getPropertyList(string $property, ?string $id = null, ?string $class = null, ?string $subProperty = null, ?\Webleit\ZohoCrmApi\Contracts\Module $module = null): Collection { - if (! $class) { + if (!$class) { $class = $this->getModelClassName() . '\\' . ucfirst(strtolower($this->inflector()->singularize($property))); } - if (! $module) { + if (!$module) { $module = $this; } - if (! $subProperty) { + if (!$subProperty) { $subProperty = $property; } @@ -260,15 +349,13 @@ protected function getPropertyList(string $property, ?string $id = null, ?string $list = $this->client->getList($url); - $collection = new Collection($list[$subProperty]); - $collection = $collection->mapWithKeys(function ($item) use ($class, $module) { + return (new Collection($list[$subProperty])) + ->mapWithKeys(function ($item) use ($class, $module) { /** @var Model $item */ $item = new $class($item, $module); return [$item->getId() => $item]; }); - - return $collection; } public function getModelClassName(): string diff --git a/src/Modules/Org.php b/src/Modules/Org.php index 9ff321b..0e4cbd5 100644 --- a/src/Modules/Org.php +++ b/src/Modules/Org.php @@ -21,6 +21,9 @@ public function getModelClassName(): string return \Webleit\ZohoCrmApi\Models\Org::class; } + /** + * @param array $params + */ public function get(string $id, array $params = []): Model { $item = $this->client->get($this->getUrl()); @@ -29,7 +32,11 @@ public function get(string $id, array $params = []): Model return $this->make([]); } - $data = array_shift($item[$this->getResourceKey()]); + /** @var array> $item */ + $item = $item[$this->getResourceKey()]; + + /** @var array $data */ + $data = array_shift($item); return $this->make($data); } diff --git a/src/Modules/Records.php b/src/Modules/Records.php index 29f0b50..ebe6e82 100644 --- a/src/Modules/Records.php +++ b/src/Modules/Records.php @@ -19,7 +19,7 @@ class Records extends Module * @param Client $client * @param string $module */ - public function __construct(Client $client, $module = '') + public function __construct(Client $client, \Webleit\ZohoCrmApi\Models\Settings\Module|string $module = '') { parent::__construct($client); @@ -30,6 +30,9 @@ public function __construct(Client $client, $module = '') $this->module = $module; } + /** + * @param array $params + */ public function get(string $id, array $params = [], ?string $externalField = null): Model { $options = []; @@ -40,9 +43,14 @@ public function get(string $id, array $params = [], ?string $externalField = nul } $item = $this->client->get($this->getUrl(), $id, $params, $options); + if (!is_array($item)) { + return $this->make(); + } + /** @var array> $items */ $items = $item[$this->getResourceKey()] ?? []; + /** @var array $data */ $data = array_shift($items); return $this->make($data ?: []); @@ -53,43 +61,64 @@ public function searchRaw(string $criteria): RecordCollection return $this->search($criteria, 'criteria'); } + /** + * @param array $params + */ public function search(string $criteria, string $key = 'criteria', array $params = []): RecordCollection { $params = array_merge($params, [$key => $criteria]); $list = $this->client->getList($this->getUrl() . '/search', $params); + if (!is_array($list)) { + return new RecordCollection([]); + } + + /** @var array> $items */ + $items = $list[$this->getResourceKey()] ?? []; - $collection = new RecordCollection($list[$this->getResourceKey()] ?? []); - $collection = $collection->mapWithKeys(function ($item) { - $item = $this->make($item); + /** @var RecordCollection $collection */ + $collection = (new RecordCollection($items)) + ->mapWithKeys(function ($data) { + /** @var array $data */ - return [$item->getId() => $item]; - }); + $item = $this->make($data); + + return [$item->getId() => $item]; + }); return $collection; } + /** + * @param array $params + */ public function searchEmail(string $criteria, array $params = []): RecordCollection { return $this->search($criteria, 'email', $params); } + /** + * @param array $params + */ public function searchPhone(string $criteria, array $params = []): RecordCollection { return $this->search($criteria, 'phone', $params); } + /** + * @param array $params + */ public function searchWord(string $criteria, array $params = []): RecordCollection { return $this->search($criteria, 'word', $params); } - public function uploadPhoto(string $recordId, string $fileName, $fileContents): bool + public function uploadPhoto(string $recordId, string $fileName, string $fileContents): bool { $result = $this->client->processResult( $this->client->call($this->getUrl() . '/' . $recordId . '/photo', 'post', [ 'multipart' => [ [ - 'name' => 'file', + 'name' => 'file', 'contents' => $fileContents, 'filename' => $fileName, ], @@ -100,13 +129,13 @@ public function uploadPhoto(string $recordId, string $fileName, $fileContents): return (($result['code'] ?? '') === Client::SUCCESS_CODE); } - public function uploadAttachment(string $recordId, string $fileName, $fileContents): bool + public function uploadAttachment(string $recordId, string $fileName, string $fileContents): bool { $result = $this->client->processResult( $this->client->call($this->getUrl() . '/' . $recordId . '/Attachments', 'post', [ 'multipart' => [ [ - 'name' => 'file', + 'name' => 'file', 'contents' => $fileContents, 'filename' => $fileName, ], @@ -117,7 +146,7 @@ public function uploadAttachment(string $recordId, string $fileName, $fileConten return (($result['code'] ?? '') === Client::SUCCESS_CODE); } - public function downloadAttachment(string $recordId, string $attachmentId, $resource): void + public function downloadAttachment(string $recordId, string $attachmentId, string $resource): void { $this->client->call($this->getUrl() . '/' . $recordId . '/Attachments/' . $attachmentId, 'get', [ 'sink' => $resource diff --git a/src/Modules/Settings.php b/src/Modules/Settings.php index 1f6ce4f..a1a71f4 100644 --- a/src/Modules/Settings.php +++ b/src/Modules/Settings.php @@ -19,15 +19,12 @@ class Settings implements \Webleit\ZohoCrmApi\Contracts\ProvidesModules, \Weblei { use ProvidesModules; - /** - * @var Client - */ - protected $client; + protected Client $client; /** - * @var array + * @var array */ - protected $availableModules = [ + protected array $availableModules = [ 'modules' => Settings\Modules::class, 'roles' => Settings\Roles::class, 'profiles' => Settings\Profiles::class, @@ -42,7 +39,7 @@ public function __construct(Client $client) $this->client = $client; } - public function __get($name) + public function __get(string $name): ?\Webleit\ZohoCrmApi\Contracts\Module { return $this->createModule($name); } diff --git a/src/Modules/Settings/Modules.php b/src/Modules/Settings/Modules.php index f4fb14e..51dd162 100644 --- a/src/Modules/Settings/Modules.php +++ b/src/Modules/Settings/Modules.php @@ -2,10 +2,11 @@ namespace Webleit\ZohoCrmApi\Modules\Settings; -use Illuminate\Support\Collection; use Webleit\ZohoCrmApi\Exception\NonExistingModule; use Webleit\ZohoCrmApi\Models\Model; +use Webleit\ZohoCrmApi\Models\Record; use Webleit\ZohoCrmApi\Modules\Module; +use Webleit\ZohoCrmApi\RecordCollection; /** * Class Taxes @@ -13,13 +14,7 @@ */ class Modules extends Module { - /** - * @param $module - * @return \Illuminate\Support\Collection|static - * @throws \Webleit\ZohoCrmApi\Exception\ApiError - * @throws \Webleit\ZohoCrmApi\Exception\GrantCodeNotSetException - */ - public function getRelatedListsForModule($module) + public function getRelatedListsForModule(\Webleit\ZohoCrmApi\Models\Settings\Module|string $module): RecordCollection { if ($module instanceof \Webleit\ZohoCrmApi\Models\Settings\Module) { $module = $module->api_name; @@ -32,13 +27,7 @@ public function getRelatedListsForModule($module) ]); } - /** - * @param $module - * @return \Illuminate\Support\Collection|static - * @throws \Webleit\ZohoCrmApi\Exception\ApiError - * @throws \Webleit\ZohoCrmApi\Exception\GrantCodeNotSetException - */ - public function getCustomViewsForModule($module) + public function getCustomViewsForModule(\Webleit\ZohoCrmApi\Models\Settings\Module|string $module): RecordCollection { if ($module instanceof \Webleit\ZohoCrmApi\Models\Settings\Module) { $module = $module->api_name; @@ -51,11 +40,7 @@ public function getCustomViewsForModule($module) ]); } - /** - * @param string|Module $module - * @return Collection - */ - public function getFieldsForModule($module): Collection + public function getFieldsForModule(\Webleit\ZohoCrmApi\Models\Settings\Module|string $module): RecordCollection { if ($module instanceof \Webleit\ZohoCrmApi\Models\Settings\Module) { $module = $module->api_name; @@ -68,15 +53,11 @@ public function getFieldsForModule($module): Collection 'module' => $module, ]); } catch (NonExistingModule $e) { - return collect([]); + return new RecordCollection([]); } } - /** - * @param string|Module $module - * @return Collection - */ - public function getLayoutsForModule($module): Collection + public function getLayoutsForModule(\Webleit\ZohoCrmApi\Models\Settings\Module|string $module): RecordCollection { if ($module instanceof \Webleit\ZohoCrmApi\Models\Settings\Module) { $module = $module->api_name; @@ -89,12 +70,7 @@ public function getLayoutsForModule($module): Collection ]); } - /** - * @param string|Module $module - * @param string $id - * @return mixed|string|\Webleit\ZohoCrmApi\Models\Model - */ - public function getLayoutForModule($module, string $id) + public function getLayoutForModule(\Webleit\ZohoCrmApi\Models\Settings\Module|string $module, string $id): Model { if ($module instanceof \Webleit\ZohoCrmApi\Models\Settings\Module) { $module = $module->api_name; @@ -107,12 +83,7 @@ public function getLayoutForModule($module, string $id) ]); } - /** - * @param string|Module $module - * @param string $id - * @return Model - */ - public function getCustomViewForModule($module, string $id): Model + public function getCustomViewForModule(\Webleit\ZohoCrmApi\Models\Settings\Module|string $module, string $id): Model { if ($module instanceof \Webleit\ZohoCrmApi\Models\Settings\Module) { $module = $module->api_name; @@ -126,17 +97,17 @@ public function getCustomViewForModule($module, string $id): Model } /** - * @param string|Module $module - * @param array $params + * @param \Webleit\ZohoCrmApi\Models\Settings\Module|string $id + * @param array $params * @return Model */ - public function get($module, array $params = []): Model + public function get(\Webleit\ZohoCrmApi\Models\Settings\Module|string $id, array $params = []): Model { - if ($module instanceof \Webleit\ZohoCrmApi\Models\Settings\Module) { - $module = $module->api_name; + if ($id instanceof \Webleit\ZohoCrmApi\Models\Settings\Module) { + $id = $id->api_name; } - return parent::get($module, $params); + return parent::get($id, $params); } public function getUrlPath(): string diff --git a/src/Modules/Users.php b/src/Modules/Users.php index 8ee13f2..cf90294 100644 --- a/src/Modules/Users.php +++ b/src/Modules/Users.php @@ -2,6 +2,7 @@ namespace Webleit\ZohoCrmApi\Modules; +use Exception; use Illuminate\Support\Collection; use Webleit\ZohoCrmApi\Enums\UserType; use Webleit\ZohoCrmApi\Models\User; @@ -31,6 +32,13 @@ public function ofType(string $type): Collection public function current(): User { - return $this->ofType(UserType::CURRENT)->first(); + $users = $this->ofType(UserType::CURRENT); + if ($users->isEmpty()) { + throw new Exception("No user found"); + } + + /** @var User $user */ + $user = $users->first(); + return $user; } } diff --git a/src/RecordCollection.php b/src/RecordCollection.php index 018d481..458023a 100644 --- a/src/RecordCollection.php +++ b/src/RecordCollection.php @@ -8,10 +8,7 @@ class RecordCollection extends Collection { - /** - * @var null|Pagination - */ - protected $pagination; + protected ?Pagination $pagination = null; public function withPagination(Pagination $pagination): self { @@ -20,7 +17,7 @@ public function withPagination(Pagination $pagination): self return $this; } - public function pagination(): Pagination + public function pagination(): ?Pagination { return $this->pagination; } diff --git a/src/Request/ListHeaders.php b/src/Request/ListHeaders.php index 10afd3b..0fc02e2 100644 --- a/src/Request/ListHeaders.php +++ b/src/Request/ListHeaders.php @@ -10,11 +10,17 @@ class ListHeaders implements Arrayable, Jsonable, JsonSerializable { - protected $headers = [ + /** + * @var array + */ + protected array $headers = [ 'If-Modified-Since' => null, 'Authorization' => null, ]; + /** + * @param array $headers + */ public function __construct(array $headers = []) { foreach ($headers as $key => $value) { @@ -22,6 +28,9 @@ public function __construct(array $headers = []) } } + /** + * @param array $headers + */ public function with(array $headers): self { foreach ($headers as $key => $value) { @@ -45,21 +54,30 @@ public function modifiedSince(DateTimeInterface $dateTime): self return $this; } + /** + * @return array + */ public function toArray(): array { return array_filter($this->headers); } - public function toJson($options = 0): string + public function toJson($options = 0): string|false { return json_encode($options); } + /** + * @return array + */ public function jsonSerialize(): array { return $this->toArray(); } + /** + * @return array + */ public function __toArray(): array { return $this->toArray(); diff --git a/src/Request/ListParameters.php b/src/Request/ListParameters.php index 496e729..ed77e65 100644 --- a/src/Request/ListParameters.php +++ b/src/Request/ListParameters.php @@ -9,7 +9,10 @@ class ListParameters implements Arrayable, Jsonable, JsonSerializable { - protected $params = [ + /** + * @var array + */ + protected array $params = [ 'fields' => null, 'ids' => [], 'sort_order' => null, @@ -23,6 +26,9 @@ class ListParameters implements Arrayable, Jsonable, JsonSerializable 'include_child' => null, ]; + /** + * @param array $params + */ public function __construct(array $params = []) { foreach ($params as $key => $value) { @@ -30,6 +36,9 @@ public function __construct(array $params = []) } } + /** + * @param string[] $fields + */ public function fields(array $fields): self { $this->params['fields'] = implode(",", $fields); @@ -37,6 +46,10 @@ public function fields(array $fields): self return $this; } + /** + * @param string[] $ids + * @return $this + */ public function ids(array $ids): self { $this->params['ids'] = implode(",", $ids); @@ -154,16 +167,22 @@ public function toArray(): array return array_filter($this->params); } - public function toJson($options = 0): string + public function toJson($options = 0): string|false { return json_encode($options); } + /** + * @return array + */ public function jsonSerialize(): array { return $this->toArray(); } + /** + * @return array + */ public function __toArray(): array { return $this->toArray(); diff --git a/src/Request/Pagination.php b/src/Request/Pagination.php index 5e676ab..dcdd6fc 100644 --- a/src/Request/Pagination.php +++ b/src/Request/Pagination.php @@ -5,11 +5,14 @@ class Pagination { - protected $perPage = 200; - protected $page = 1; - protected $count = 0; - protected $moreRecords = false; - + protected int $perPage = 200; + protected int $page = 1; + protected int $count = 0; + protected bool $moreRecords = false; + + /** + * @param array{"per_page"?: int, "page"?: int, "count"?: int, "more_records"?: bool} $params + */ public function __construct(array $params = []) { $this->perPage = $params['per_page'] ?? $this->perPage; diff --git a/src/ZohoCrm.php b/src/ZohoCrm.php index f886c55..d796933 100644 --- a/src/ZohoCrm.php +++ b/src/ZohoCrm.php @@ -18,50 +18,38 @@ class ZohoCrm implements Contracts\ProvidesModules { use ProvidesModules; - /** - * @var Client - */ - protected $client; - - /** - * @var array - */ - protected $totals = []; + protected Client $client; /** - * @var array + * @var class-string[] */ - protected $availableModules = [ + protected array $availableModules = [ 'settings' => Modules\Settings::class, 'users' => Modules\Users::class, 'org' => Modules\Org::class, - 'records' => Records::class, + 'records' => \Webleit\ZohoCrmApi\Modules\Records::class, 'leads' => Modules\Leads::class, ]; - /** - * @var Collection - */ - protected $apiModules; + /** @var Collection|null */ + protected ?Collection $apiModules; public function __construct(Client $client) { $this->client = $client; } - /** - * @param $name - * @return Contracts\Module|Records|null - */ - public function __get($name) + public function __get(string $name): ?Records { + /** @var Records|null $module */ $module = $this->createModule($name); - if ($module) { - return $module; + if (!$module) { + $module = $this->createRecordsModule($name); } - return $this->createRecordsModule($name); + /** @var Records|null $module */ + return $module; } public function createRecordsModule(string $name): Records @@ -69,11 +57,16 @@ public function createRecordsModule(string $name): Records return new Records($this->getClient(), $name); } + /** + * @return Collection + * @throws Exception\InvalidResourceKey + */ public function getApiModules(): Collection { if (! $this->apiModules) { - $this->apiModules = $this->settings->modules->getList()->mapWithKeys(function (Module $module) { - return collect([strtolower($module->api_name) => $module]); + $this->apiModules = $this->settings->modules->getList()->mapWithKeys(function ($module) { + /** @var Module $module */ + return [strtolower($module->api_name) => $module]; }); } diff --git a/tests/ApiTest.php b/tests/ApiTest.php index 53d9155..6161cdb 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -408,7 +408,7 @@ public function canGetCurrentUser() $user = self::$zoho->users->current(); $this->assertNotNull($user); - $users = self::$zoho->users->ofType(UserType::current()); + $users = self::$zoho->users->ofType(UserType::CURRENT); $this->assertTrue($users->contains($user)); } From 59169f5b84edc2203552beaaf1e52ef63a8c6784 Mon Sep 17 00:00:00 2001 From: Daniele Rosario Date: Tue, 31 Jan 2023 18:27:14 +0100 Subject: [PATCH 3/5] wip --- src/Modules/Module.php | 2 +- src/ZohoCrm.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Modules/Module.php b/src/Modules/Module.php index 24fab2b..27ef8c3 100644 --- a/src/Modules/Module.php +++ b/src/Modules/Module.php @@ -143,7 +143,7 @@ public function update(string $id, array $data, array $params = [], array $trigg $data = $this->client->put($this->getUrl(), $data, $params); $data = $data['data'] ?? []; - $row = array_shift($data['data']); + $row = array_shift($data); return $this->make($row['details']); } diff --git a/src/ZohoCrm.php b/src/ZohoCrm.php index d796933..447400c 100644 --- a/src/ZohoCrm.php +++ b/src/ZohoCrm.php @@ -39,16 +39,16 @@ public function __construct(Client $client) $this->client = $client; } - public function __get(string $name): ?Records + public function __get(string $name): ?Modules\Module { - /** @var Records|null $module */ + /** @var Module|null $module */ $module = $this->createModule($name); if (!$module) { $module = $this->createRecordsModule($name); } - /** @var Records|null $module */ + /** @var Module|null $module */ return $module; } From a86338bf0df58bc6b4d08581ec214e9755a9e3cf Mon Sep 17 00:00:00 2001 From: Daniele Rosario Date: Mon, 13 Feb 2023 16:00:33 +0100 Subject: [PATCH 4/5] wip --- src/Modules/Module.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Modules/Module.php b/src/Modules/Module.php index 27ef8c3..6139a6d 100644 --- a/src/Modules/Module.php +++ b/src/Modules/Module.php @@ -111,13 +111,13 @@ public function createMany(mixed $data, array $params = [], array $triggers = [ $results = []; foreach ($data as $row) { - $item = $row; - if (($row['code'] ?? '') === Client::SUCCESS_CODE) { - $item = $this->make($row['details'] ?? []); + if (($row['code'] ?? '') !== Client::SUCCESS_CODE) { + throw new \Exception(json_encode($row), $row['code'] ?? 500); + } - $results[] = $item; + $results[] = $this->make($row['details'] ?? []); } return collect($results); From 922a8d880d3d0b4f6945241dd7a49c445d636b5b Mon Sep 17 00:00:00 2001 From: Daniele Rosario Date: Fri, 28 Jun 2024 14:26:29 +0200 Subject: [PATCH 5/5] fix --- src/Modules/Module.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/Module.php b/src/Modules/Module.php index 6139a6d..ad97f64 100644 --- a/src/Modules/Module.php +++ b/src/Modules/Module.php @@ -113,7 +113,7 @@ public function createMany(mixed $data, array $params = [], array $triggers = [ foreach ($data as $row) { if (($row['code'] ?? '') !== Client::SUCCESS_CODE) { - throw new \Exception(json_encode($row), $row['code'] ?? 500); + throw new \Exception(json_encode($row), 500); }