tristanjahier/zoho-crm

Zoho CRM API 的 PHP 客户端。

0.5.0 2023-09-03 17:08 UTC

This package is auto-updated.

Last update: 2024-09-23 17:11:24 UTC


README

这是一个 Zoho CRM 的 API 客户端库,用 PHP 编写。

它旨在覆盖整个 API(每个模块和方法),同时提供优秀的抽象和非常易于使用的函数。

要求

安装

推荐通过 Composer 安装此包。

编辑您的 composer.json 文件

"require": {
    "tristanjahier/zoho-crm": "^0.5"
}

或者直接运行以下命令

composer require tristanjahier/zoho-crm

如果您不知道“PSR-7、PSR-17 和 PSR-18 实现”是什么,基本上意味着您需要一个符合标准规范的 HTTP 客户端库。如果您不想麻烦挑选一个,我们推荐您安装流行的 Guzzle

composer require guzzlehttp/guzzle

入门

TL;DR - 简要示例

这里只展示了使用此库可以完成的一些操作示例

// Create an API client
$client = new Zoho\Crm\V2\Client(
    new Zoho\Crm\V2\AccessTokenBroker('MY_API_CLIENT_ID', 'MY_API_CLIENT_SECRET', 'MY_API_REFRESH_TOKEN')
);

// Create a request and execute it
$response = $client->newRawRequest('Calls')->param('page', 2)->execute();

// Retrieve all deals modified for the last time after April 1st, 2019
$deals = $client->records->deals->all()->modifiedAfter('2019-04-01')->get();

// Retrieve records by ID
$myLead = $client->records->leads->find('1212717324723478324');
$myProduct = $client->records->products->find('8734873457834574028');

// Create a new contact
$result = $client->records->contacts->insert([
    'First_Name' => 'Jean',
    'Last_Name' => 'Dupont',
    'Email' => 'jacques@dupont.fr'
]);

$contactId = $result['details']['id'];

// Update the name of the contact
$client->records->contacts->update($contactId, ['First_Name' => 'Jacques']);

// Delete this contact
$client->records->contacts->delete($contactId);

基础

此库的主要组件是 Client 类。这是每个 API 请求的 起点

要创建客户端对象,首先需要一个“访问令牌中介”对象。这个对象唯一的目的就是为您的客户端提供新鲜的 API 访问令牌。它必须实现 Zoho\Crm\Contracts\AccessTokenBrokerInterface

Zoho\Crm\V2\AccessTokenBroker 是默认实现,应该适用于大多数用例。要创建一个实例,您需要提供已注册 API 客户端的凭证。按照这个顺序,客户端 ID、客户端密钥和刷新令牌

$tokenBroker = new Zoho\Crm\V2\AccessTokenBroker('MY_API_CLIENT_ID', 'MY_API_CLIENT_SECRET', 'MY_API_REFRESH_TOKEN');

然后您可以使用该令牌中介创建您的客户端

$client = new Zoho\Crm\V2\Client($tokenBroker);

这样就可以开始向 Zoho CRM 的 API 发送请求了。然而,在这种配置下,API 访问令牌(用于认证请求)将仅存在于 $client 实例的生命周期内。这意味着一旦您的客户端被垃圾回收或 PHP 脚本停止执行,您就会失去访问令牌,即使它可能仍然在接下来的几分钟内有效!

为了防止浪费新鲜的访问令牌,强烈建议使用“访问令牌存储”来启用跨多个 PHP 生命周期的持久性

  1. 令牌存储是一个仅用于处理客户端使用的访问令牌存储的对象。
  2. 它必须实现 Zoho\Crm\Contracts\AccessTokenStoreInterface
  3. 它应作为客户端构造函数的第二个参数传递。

此库在 Zoho\Crm\AccessTokenStorage 中提供了一些实现。要快速入门,您可以使用 FileStore,正如其名称所示,它只是将访问令牌存储在本地文件系统中。示例

$tokenStore = new Zoho\Crm\AccessTokenStorage\FileStore('dev/.token.json');
$client = new Zoho\Crm\V2\Client($tokenBroker, $tokenStore);

最后,您需要确保您的客户端有一个有效的访问令牌(未过期)

if (! $client->accessTokenIsValid()) {
    $client->refreshAccessToken();
}

现在您就可以开始进行 API 请求了!一种可能性是遵循 API 的 HTTP 规范,手动构建您想要的任何请求,使用“原始请求”

// Retrieve the second page of records from the Contacts module, modified after April 1st, 2019:
$request = $client->newRawRequest()
    ->setHttpMethod('GET')
    ->setUrl('Contacts')
    ->setHeader('If-Modified-Since', '2019-04-01')
    ->setUrlParameter('page', 2);

// Retrieve a Deals record whose ID is 9032776450912388478:
$request = $client->newRawRequest('Deals/9032776450912388478');

创建请求对象不会向 API 发送任何 HTTP 请求,您需要执行它

$response = $request->execute();

如果请求成功,它将返回一个 Response 实例。API 响应已为您解析并清理,您只需调用 getContent() 即可获取数据。

$data = $response->getContent();

全部总结

$response = $client->newRawRequest()
    ->setHttpMethod('GET')
    ->setUrl('Contacts')
    ->setHeader('If-Modified-Since', '2019-04-01')
    ->setUrlParameter('page', 2)
    ->execute();

$records = $response->getContent();

如果您不想麻烦使用正式的 Response 对象,您可以在任何请求上调用 get() 方法。它将执行请求并返回其响应内容。

$data = $request->get();
// is strictly equivalent to:
$data = $request->execute()->getContent();

但是...这仍然有点啰嗦,不是吗? 是的。这只是最基本的进行 API 请求的方式。阅读下一节,了解如何更好地使用库。

客户端子 API

API 支持分为 "子 API",它们是将 API 的多个相关功能重新组合的帮助程序。它们附加到客户端,您可以通过公共属性访问它们(例如:$client->theSubApi)。目前可用的子 API 有

子 API 的目的是为开发者提供一个流畅、优雅和简洁的接口,以操作 API 的一或多个相关方面。

例如,让我们考虑上一个示例中的一个请求,从 2019 年 4 月 1 日之后修改的联系人模块的第二页记录

$records = $client->newRawRequest()
    ->setHttpMethod('GET')
    ->setUrl('Contacts')
    ->setHeader('If-Modified-Since', '2019-04-01')
    ->setUrlParameter('page', 2)
    ->get();

使用记录子 API,可以重写如下

$records = $client->records->contacts->all()->modifiedAfter('2019-04-01')->page(2)->get();

创建一个新的联系人非常简单

$result = $client->records->contacts->insert([
    'First_Name' => 'Jean',
    'Last_Name' => 'Dupont',
    'Email' => 'jean.dupont@exemple.fr'
]);

检索您的 Zoho CRM 组织中的所有用户非常简单

$users = $client->users->all()->get();

这只是几个例子。子 API 带来了更多功能。请查看专用文档并探索代码以了解更多信息。

请求分页

当从 Zoho 请求记录时,每个响应最多返回 200 条记录。因此,如果您想获取超过 200 条记录,您需要多次请求。这通过 "页" URL 参数完成。在参数上进行迭代称为 分页

在这个库中,由于一个名为 autoPaginated() 的请求方法,分页变得简单。您只需在兼容的请求对象(实现 Zoho\Crm\Contracts\PaginatedRequestInterface)上调用此方法,库将获取所有页面记录,直到没有更多数据(或您设置的限制)。示例

$client->records->contacts->newListRequest()->autoPaginated()->get();

注意

记录和用户子 API 的 all() 方法返回的请求对象已启用自动分页。

默认情况下,请求分页是同步的。这意味着只有在上一页执行并返回响应后,才会获取新的一页。此库还支持异步请求执行,通常使分页更快。 这再次非常简单。您只需在请求上调用 concurrency() 方法即可。

$client->records->calls->newListRequest()->autoPaginated()->concurrency(5)->get();
// or
$client->records->calls->all()->concurrency(5)->get();

此方法接受一个单一参数:一个正的非零整数(> 0)。它是并发 API 请求的数量。如果您传递 1,则分页将是同步的。您还可以传递 null 来禁用异步分页。

异步分页可以根据并发设置大大加快分页请求。如果您需要检索数千条记录,它将节省大量执行时间。

警告

使用 X 个并发请求,您可以浪费多达 X-1 个 API 请求。请明智使用。

响应类型

响应内容的类型取决于您使用的子 API 方法。它可以是一个标量,如字符串、数组、布尔值或 null。但在大多数情况下,您将得到一个实体或实体集合。

实体是包含一组连贯数据的对象。例如,Zoho 记录(联系人、电话、潜在客户等)是实体。

当响应包含(或应包含)多个实体时,您将得到一个 实体集合

// Returns a single entity of type Zoho\Crm\V2\Records\Record:
$client->records->calls->find('<record ID>');

// Returns a collection of entities (Zoho\Crm\V2\Users\User):
$client->users->all()->get();

实体

实体是 Zoho\Crm\Entities\Entity(或其任何子类)的实例。

它封装了常见API对象(例如记录或用户)的属性。

它提供了一些有用的方法:

  • has($attribute):检查属性是否已定义
  • get($attribute):获取属性的值
  • set($attribute, $value):设置属性的值
  • getId():获取实体ID
  • toArray():获取原始属性数组

它实现了魔法方法 __get()__set(),这使得您可以像公共属性一样操作其属性

$id = $contact->id;
$familyName = $contact->Last_Name;
$contact->Phone = '+1234567890';

实体集合

实体集合是 Zoho\Crm\Entities\Collection 的实例。

集合是一个数组包装器,它提供了一个流畅的接口来操作其项。在实体集合的情况下,这些项是实体。

它提供了一组有用的方法。以下是一些例子:

  • has($key):确定给定索引处是否存在项
  • get($key, $default = null):获取给定索引处的项
  • count():获取集合中的项数
  • isEmpty():确定集合是否为空
  • first(callable $callback = null, $default = null):获取集合中的第一个项
  • firstWhere($key, $operator, $value = null):获取匹配给定(键,[运算符,]值)元组的第一个项
  • last(callable $callback = null, $default = null):获取集合中的最后一个项
  • lastWhere($key, $operator, $value = null):获取匹配给定(键,[运算符,]值)元组的最后一个项
  • map(callable $callback):对每个项应用回调并返回一个包含结果的新集合
  • sum($property = null):计算项的总和
  • filter(callable $callback = null):使用回调过滤集合项
  • where($key, $operator, $value = null):根据比较元组(键,[运算符,]值)过滤项
  • pluck($value, $key = null):通过键获取给定项属性值

有关详细信息,请查看 Zoho\Crm\Support\Collection 的代码。

它实现了 ArrayAccessIteratorAggregate,这使得您可以像数组一样操作它

// If $records is an instance of Zoho\Crm\Entities\Collection...

// You can access items with square brackets:
$aRecord = $records[2];
$records[] = new Zoho\Crm\V2\Records\Record(['Phone' => '+1234567890']);

// And you can loop through it:
foreach ($records as $record) {
    ...
}

子API引用

记录

记录子API提供了一个方法,即 module(),用于创建 Zoho\Crm\V2\Records\ModuleHelper 的实例,该实例提供与记录相关的各种功能。

$client->records->module('Contacts');
$client->records->module('Calls');
$client->records->module('Deals');
$client->records->module('My_Custom_Module');

它还实现了魔法方法 __get(),这样您就可以使用驼峰命名的模块名称作为公共属性来获取模块助手。

$client->records->contacts;
$client->records->calls;
$client->records->deals;
$client->records->priceBooks;

本节剩余部分详细介绍了模块助手上的方法。

all()

Zoho\Crm\V2\Records\ListRequest 的实例,启用自动分页。

$client->records->deals->all();

deleted()

Zoho\Crm\V2\Records\ListDeletedRequest 的实例,启用自动分页。

$client->records->deals->deleted();

search(string $criteria)

Zoho\Crm\V2\Records\SearchRequest 的实例,启用自动分页。

$client->records->deals->search('<Search criteria>');

searchBy(string $field, string $value)

Zoho\Crm\V2\Records\SearchRequest 的实例,启用自动分页。

$client->records->deals->searchBy('Field', 'value');
// is shorthand for:
$client->records->deals->search('(Field:equals:value>)');

relationsOf(string $recordId, string $relatedModule)

列出与给定记录相关的另一个模块中的记录。是 Zoho\Crm\V2\Records\ListRelatedRequest 的实例,启用自动分页。

$client->records->deals->relationsOf('<Deal ID>', 'Contacts');

relatedTo(string $relatedModule, string $recordId)

列出从另一个模块获取的与给定记录相关的记录。是 relationsOf() 的逆操作。是 Zoho\Crm\V2\Records\ListRelatedRequest 的实例,启用自动分页。

$client->records->deals->relatedTo('Contacts', '<Contact ID>');

find(string $id)

通过其ID检索记录。

$record = $client->records->calls->find('Record ID');

返回一个 Zoho\Crm\V2\Records\Record 的实例,如果未找到则返回 null

insert($record, array $triggers = null)

插入新记录。

接受一个 Record 实例或属性数组的数组。

$client->records->calls->insert([
    'Field_1' => 'Value 1',
    'Field_2' => 'Value 2',
    ...
]);

返回一个包含操作结果信息的数组。

insertMany($records, array $triggers = null)

同时插入多个新记录。

$records = [
    [
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ], [
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ],
    ...
];

$client->records->calls->insertMany($records);

返回一个包含操作结果信息的数组数组。

update(string $id, $data, array $triggers = null)

更新现有记录。

$client->records->calls->update('Record ID', [
    'Field_1' => 'Value 1',
    'Field_2' => 'Value 2',
    ...
]);

返回一个包含操作结果信息的数组。

updateMany($records, array $triggers = null)

同时更新多个现有记录。

$records = [
    [
        'id' => 'Record 1 ID',
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ], [
        'id' => 'Record 2 ID',
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ],
    ...
];

$client->records->calls->updateMany($records);

返回一个包含操作结果信息的数组数组。

upsert($record, array $duplicateCheckFields = null, array $triggers = null)

更新或插入记录。

$client->records->calls->upsert([
    'Field_1' => 'Value 1',
    'Field_2' => 'Value 2',
    ...
], ['Field_1']);

返回一个包含操作结果信息的数组。

upsertMany($records, array $duplicateCheckFields = null, array $triggers = null)

同时更新或插入多个记录。

$records = [
    [
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ], [
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ],
    ...
];

$client->records->calls->upsertMany($records, ['Field_1']);

返回一个包含操作结果信息的数组数组。

delete(string $id)

删除记录。

$client->records->calls->delete('Record ID');

返回一个包含操作结果信息的数组。

deleteMany(array $ids)

同时删除多个记录。

$client->records->calls->deleteMany(['Record 1 ID', 'Record 2 ID']);

返回一个包含操作结果信息的数组数组。

用户

all()

启用自动分页的 Zoho\Crm\V2\Users\ListRequest 实例。

$client->users->all();

高级主题

使用不同的API端点

默认端点是 https://www.zohoapis.com/crm/v2/。您可能想使用另一个:[https://www.zoho.com/crm/developer/docs/api/v2/multi-dc.html](https://www.zoho.com/crm/developer/docs/api/v2/multi-dc.html)。

为此,您可以使用 setEndpoint() 方法

$client->setEndpoint('https://www.zohoapis.eu/crm/v2/');

同样,如果您使用的是默认访问令牌代理,您还可以更改授权端点

$client->getAccessTokenBroker()->setAuthorizationEndpoint('https://accounts.zoho.eu/oauth/v2/');

自动刷新访问令牌

默认情况下,您需要自己处理访问令牌的有效性。这意味着您需要定期检查过期日期,并在令牌过期时请求新的令牌。

客户端有一个选项,可以在需要时自动刷新其访问令牌。您只需将 'access_token_auto_refresh_limit' 预设设置为在刷新之前应该尽快刷新的剩余有效秒数。

$client->preferences()->set('access_token_auto_refresh_limit', 60);

在上面的例子中,当客户端需要向API发出请求并且其当前访问令牌将在一分钟内过期时,它将请求新的访问令牌。

请求执行前后的钩子

如果您需要,可以注册一个在每次请求之前或之后执行的回调。

在两种情况下,回调都是一个接受两个参数的闭包或任何 callable

  1. 请求对象的副本;
  2. 执行的唯一ID(随机16个字符字符串),以防您需要匹配“之前”和“之后”的钩子。

使用 beforeEachRequest() 方法注册一个将在每个请求执行之前调用的回调,但仅在请求成功验证后

使用 afterEachRequest() 方法注册一个将在每个请求执行后调用的回调,API已返回响应。 如果HTTP请求层抛出错误或异常,则不会调用回调。

示例

use Zoho\Crm\Contracts\RequestInterface;

$client->beforeEachRequest(function (RequestInterface $request, string $execId) {
    // do something...
});

$client->afterEachRequest(function (RequestInterface $request, string $execId) {
    // do something...
});

注意

分页请求不会直接触发这些钩子,但它们的后续请求(每页)会。换句话说,只有直接导致API HTTP请求的请求才会触发钩子。

您可以在注册时唯一标识回调

$client->beforeEachRequest(function () {}, id: 'logging');
$client->afterEachRequest(function () {}, id: 'logging');

然后您可以注销这些已标识的回调,这样它们就永远不会再次被调用

$client->cancelBeforeEachRequestCallback('logging');
$client->cancelAfterEachRequestCallback('logging');

请求中间件

如果您需要,可以注册自定义中间件,该中间件将在请求转换为HTTP请求之前应用于每个请求。与执行钩子不同,中间件可以修改请求对象。实际上,这正是中间件的目的。

使用 registerMiddleware() 方法,它只接受一个 callable。因此,您可以传递一个闭包或实现 Zoho\Crm\Contracts\MiddlewareInterface 的对象。

示例

use Zoho\Crm\Contracts\RequestInterface;

$client->registerMiddleware(function (RequestInterface $request) {
    $request->setUrlParameter('toto', 'tutu');
});

请注意,您不需要返回请求对象。实际上,返回值将被简单地忽略。

注意

与执行钩子类似,分页请求不会直接通过中间件,但它们的后续请求(每页)会。