tristanjahier / zoho-crm-php
Requires
- php: ^7.3|^8.0
- doctrine/inflector: ^1.4|^2.0
- guzzlehttp/guzzle: ^6.2|^7.0
Requires (Dev)
- phpunit/phpunit: ^9.0
- symfony/var-dumper: ^5.0
This package is auto-updated.
Last update: 2024-03-23 16:08:24 UTC
README
这是一个Zoho CRM的API客户端库,使用PHP编写。
它旨在覆盖整个API(每个模块和方法),同时提供出色的抽象和非常易于使用的函数。
需求
- PHP :
8.0+
- PSR-7 (HTTP消息)、PSR-17 (HTTP工厂)和PSR-18 (HTTP客户端)的实现
安装
推荐通过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生命周期的持久性
- 令牌存储是一个仅用于处理客户端使用的访问令牌的对象。
- 它必须实现
Zoho\Crm\Contracts\AccessTokenStoreInterface
。 - 它应作为客户端构造函数的第二个参数传递。
此库在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()
:获取实体IDtoArray()
:获取原始属性数组
它实现了魔术方法__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
的代码以获取更多详细信息。
它实现了ArrayAccess
和IteratorAggregate
,这使得你可以像数组一样操作它
// 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
- 请求对象的副本;
- 执行的唯一 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'); });
请注意,您不需要返回请求对象。实际上,返回值将被简单地忽略。
注意
与执行钩子类似,分页请求不会直接通过中间件,但它们的后续请求(每页)会。