tristanjahier/zoho-crm-php

此包已被弃用,不再维护。作者建议使用tristanjahier/zoho-crm包。

这是Zoho CRM API的PHP客户端。

0.5.0 2023-09-03 17:08 UTC

This package is auto-updated.

Last update: 2024-03-23 16:08: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有

客户端访问器
记录 Zoho\Crm\V2\Records\SubApi
用户 Zoho\Crm\V2\Users\SubApi

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

例如,让我们考虑一个先前的示例中的请求,从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

为此,您可以使用 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发出请求且当前访问令牌将在一分钟内过期时请求新的访问令牌。

请求执行前后的钩子

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

在这两种情况下,回调都是一个接受 2 个参数的闭包或任何 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');
});

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

注意

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