igorsantos07 / prosperworks
ProsperWorks CRM API的非官方(但相当不错)SDK
Requires
- php: ^7.0
- doctrine/inflector: ^1.2
- guzzlehttp/guzzle: ^6.3
Requires (Dev)
- phpunit/phpunit: ^6.2
README
布局 |
---|
默认 |
简介
实施时,ProsperWorks没有SDK,我们需要执行大量操作来从我们的旧CRM迁移数据并与其他子系统同步信息,因此我们设计了一些类来封装API操作。它使用Guzzle,但最终变得过于庞大,所以我们将其变成了一个独立的库。
该项目最初由igorsantos07编写,现在由smith-carson(网站)维护。
安装
1. 在项目中安装
您需要运行PHP 7 (是的,自2015年12月以来它已经稳定,并包含许多有用的功能,现在就升级!)。
要通过Composer将其添加到您的项目中,请运行composer require igorsantos07/prosperworks
。如果您的min-requirements
是“稳定”或“dev-master”,它将获取最稳定的版本。
2. 配置包
为了使SDK运行,需要配置一些内容
API凭证
目前,ProsperWorks上没有“API用户”,因此您需要一个活动用户来与API通信。
-
登录ProsperWorks,转到设置 > 偏好 / API密钥。在那里,您可以为登录用户生成一个新的API密钥。复制该密钥以及用户电子邮件。
-
创建一个引导文件,或者将以下代码包含在项目配置部分的以下代码中:
\ProsperWorks\Config::set($email, $token)
。
Webhooks参数
(可选)如果您打算使用Webhooks从ProsperWorks获取更新,您还需要在该方法中输入三个更多参数
- Webhooks密钥,将用于防止意外调用您的路由。这应该是一个纯文本字符串。
- 根URL。这可能是您用于系统的相同域名/路径,ProsperWorks将向其POST。有关Webhooks部分的更多信息。
- 加密对象。它应该响应
encryptBase64()
和decryptBase64()
,两者都接收和返回字符串(它还可以实现\ProsperWorks\Interfaces\Crypt
以简化操作)。它将用于发送加密密钥,并对其进行解密以确保接收到的调用来自ProsperWorks(或者至少拥有加密密钥的人)。
缓存对象
(可选)要使某些部分更快,您还可以使用第六个参数输入缓存层。它是一个需要响应get()
和save()
的对象,或者实现\ProsperWorks\Interfaces\Cache
。
主要用于缓存(1小时)API的元数据,例如自定义字段、活动类型和联系人类型等。这些信息很少改变,因此可以安全地缓存,从而加快调用速度(否则,对于每个带有自定义字段的资源,我们还需要从自定义字段端点检索)。
3. 调试模式
在导入脚本和类似任务期间,查看网络流量并检查您所期望的操作是否正确执行可能很有用。您可以通过调用 ProsperWorks\Config::debugLevel()
启用库的调试信息 echo
。
ProsperWorks\Config::DEBUG_BASIC
- 将触发一些消息,例如 "POST /people/search",以便您知道哪些请求正在发送。它还会警告达到速率限制。
ProsperWorks\Config::DEBUG_COMPLETE
- 除了上面提到的所有功能外,还包括完整的请求有效负载。
null
、false
、0
或ProsperWorks\Config::DEBUG_NONE
- 将停止打印消息。
这不需要与
Config::set()
一起完成;它可以在任何地方发生,并将从该部分开始改变行为。
提示:“沙盒”账户
一段时间后,在首次实施此库时,我们与支持代表就沙盒环境的缺乏进行了交谈。他们建议我们创建一个试用账户,并用该账户代替付费账户上的用户,并告知支持部门正在使用该账户进行API实现测试 - 因此,他们会将该单个账户的试用期延长至所需时间。
API通信
大部分操作都是通过 \ProsperWorks\CRM
抽象类完成的,以及从它产生的对象(您可以考虑它为某种类型的工厂类)。例外的是Webhooks,它们有一个特殊的端点类,使其更容易实现。
提示:ProsperWorks API 文档
您可能想阅读 REST API 文档,以了解构成此SDK的内构组件。
配置就绪后,通过简单的流畅API进行ProsperWorks API调用。大多数端点表现相同,特殊案例是账户和大多数元数据端点。
在以下示例中,我们将考虑已导入当前命名空间中的类。
常见端点
对 CRM
的单个、空静态调用会返回一个 Endpoint
对象(请参阅 保存实例),允许您运行所有常见操作
<?php //runs GET /people/10 to retrieve a single record $people = CRM::person()->find(10); //runs GET /people multiple times (it's paged) until all entries are retrieved $people = CRM::person()->all(); //there's no such operation in some endpoints; all() runs an empty /search, instead //runs POST /people to generate a new record $newPerson = CRM::person()->create(['name' => 'xxx']); //runs PUT /people/25 to edit a given record $person = CRM::person()->edit(25, ['name' => 'xxx']); //runs DELETE /people/10 to destroy that record $bool = CRM::person()->delete(10); //runs POST /people/search with the given parameters until all entries are found (it's paged) $people = CRM::person()->search(['email' => 'test@example.com']);
所有成功调用都将返回一个 BareResource
对象,其中包含该端点上的所有信息,或这些对象的列表。有关详细信息,请参阅 响应对象。
如果失败,将给出错误消息。(待办事项:提供引发异常的选项)
还有一些快捷方式,例如
<?php //plural calls do the same as the singular all() call $people = CRM::people(); //same as CRM::person()->all() $tasks = CRM::tasks(); //same as CRM::task()->all() $companies = CRM::companies(); //same as CRM::company()->all() //there's also two other types of magic calls $people = CRM::person(23); //same as CRM::person()->find(23) $people = CRM::person(['country' => 'US']); //same as CRM::person()->search(...)
特殊案例:受限端点
所有元数据资源(在文档中称为 二级资源),以及 Account
端点,仅具有读取权限。尚未进行有效操作的验证(请参阅 #7)。以下是这些只读端点的列表,可通过复数调用(例如 CRM::activityTypes()
)访问,除了 Account
是单数之外
- Account(您必须以单数形式调用)
- 活动类型
- 联系人类型
- 自定义字段
- 客户来源
- 损失原因
- 管道
- 管道阶段
元数据快捷方式
由于这些端点大多是列表,您也可以通过可缓存的 CRM::fieldList()
方法访问这些数据,该方法以更组织化的方式返回信息
<?php $types = CRM::fieldList('contactType'); //singular! print_r($types); // gives an array of names, indexed by ID: // ( // [123] => Potential Customer // [124] => Current Customer // [125] => Uncategorized // [126] => Former Customer // ) echo CRM::fieldList('contactType', 524131); //search argument // prints "Potential Customer". That argument searches on both sides of the array $actTypes = CRM::fieldList('activityType', null, true); //asks for "detailed response" print_r($actTypes); // gives the entire resources, still indexed by ID // [166] => stdClass Object // ( // [id] => 166 // [category] => user // [name] => Social Media // [is_disabled] => // [count_as_interaction] => 1 // ) // // [...] // )
合理性警告:那里的ID是样本;每个ProsperWorks客户的ID都不同。
还值得一提的是,一些字段是从API“翻译”到特定对象的,例如时间戳、地址、自定义字段等,因此您可能永远不需要直接处理自定义字段端点。有关该信息的更多信息,请参阅子资源和响应对象部分。
相关项目
存在一个统一的API,用于在两个资源之间创建链接。因此,每个资源对象都有自己的 related
方法,用于操作这些链接。由于这是一个非常简单的API,您只能列出、创建和删除关系。请参阅相关项目文档,以了解关系限制 - 一些资源只允许一个链接,并且并非每个资源都与每个其他资源有联系。
<?php // you always have to feed the origin resource ID to related() // and then call the operation you want, like: $tasks = CRM::task()->related(10)->all(); //lists all $task_projects = CRM::task()->related(22)->projects(); //lists specific type $task_project = CRM::task()->related(22)->create(10, 'project'); //create one $task_project = CRM::task()->related(22)->delete(27, 'project'); //and remove
批量操作
还可以运行批量操作,使用Guzzle的并发功能来加速并行调用。一些单次使用的方法有一个*Many对应物,例如
createMany()
- 简单;而不是有效载荷,您传递一个有效载荷列表
editMany()
- 在这种情况下,您必须按ID索引传递一个有效载荷列表。
delete()
- 是特殊的,因为它可以处理任意数量的ID。其响应将根据参数数量而变化。
您可以使用数组、迭代器或生成器来使用这些,并且它将负责同时运行多达10 (未来:可配置) HTTP调用。
例如,让我们基于查询结果(这也具有低内存使用率)创建许多任务条目,然后删除这些条目
<?php //this call is using a simple generator $thousandsOfTasksQueryResult = [...]; $allTasks = CRM::task()->createMany(function() use ($thousandsOfTasksQueryResult) { foreach ($thousandsOfTasksQueryResult as $task) { yield [ 'name' => $task->name, 'due_date' => $task->dueDate->format('U'), 'status' => $task->completed? 'Completed' : 'Open' ]; } }); // as that's a batch operation, it seemed unsafe to throw harsh errors. // thus, success will give an object of data, while errors return a simple message $toDelete = []; foreach ($allTasks as $response) { if (is_object($response) { $toDelete[] = $response->id; } else { $logger->warning("Couldn't create Task: $response"); } } //here we use a plain list of arguments: you have to unpack the array CRM::task()->delete(...$toDelete); }
在这种情况下,生成器特别有用,因为它将通过不在内存中存储大量有效载荷/请求来节省大量内存。
批量关系操作
类似于批量API调用,也可以运行一系列关系更改。为此,使用relatedBatch()
的方法,使用一个ID + 类型(或Relation
辅助对象)的列表,按原始ID索引
<?php use ProsperWorks\SubResources\Relation; $relClientsQuery = [...]; CRM::task()->relatedBatch()->create(function() use ($relClientsQuery, $pwTaskId) { foreach ($relatedClientsQuery as $client) { // this would generate an array of Relation() objects, indexed by the same ID // causes no error; this won't become a real array (thus, with keys conflicts) yield new $pwTaskId => new Relation($client->id, 'company'); // the following would also work //yield new $pwTaskId => ['id' => $client->id, 'type' => 'company']; } });
我认为所有这些静态调用都不是性能良好的
确实,在非常小的范围内,它们可能不是。您始终可以使用半路对象来运行常见操作,例如当您正在同一端点运行大量操作时。然而,静态调用将让您免于在一次性的调用中保留几个配置/实例;(笑)
<?php $peopleRes = CRM::person(); $client = $peopleRes->find($clientId); $tags = array_merge($client->tags, 'new tag'); $peopleRes->edit($clientId, compact('tags'));
速率限制
SDK内还内置了速率限制拦截器,因此当它注意到即将达到限制时,它会稍微“sleep”,以便在限制释放后不久允许新的操作。当调试模式开启时,在CLI上发出一些通知。这对于批量操作特别有用。
响应对象
大多数(所有?)响应将是一个BareResource
对象,或者这些对象的一个列表。最大的优点是这个类的“翻译”功能:它通过利用结构更简单/可预测的对象,或者在一些数据验证/翻译(即子资源)的基础上,使有效载荷的一些部分更容易使用。
- 大多数日期字段(date_created、due_date、date_last_contacted等)将转换为
DateTime
对象。有一个实际上并不工作的设置来禁用这一点,在BareResource中(参见#8) - 如果有的话,将根据
contact_type_id
生成与contact_type
相关的名称 - 自定义字段将生成两个条目
custom_fields_raw
,包含API的原始有效载荷custom_fields
,包含一个CustomField
对象的列表,按字段名称索引
- 其他一些复杂结构也将成为子资源对象,例如
子资源
有一些依赖对象不能直接用于API调用,但它们是主资源的一部分。大多数情况下,这些是JSON有效载荷内的内部文档。它们用于响应(参见响应对象),但它们也被设计为使您的调用更容易,在信息之间“翻译”,并确保您始终遵循对那些子文档的请求规则。
特别地,实现了TranslateResource
特质的子资源将允许对某些受保护字段(列在$altFields
中)的读取访问。简而言之,当您在PHP中将对象转换为数组时(这是我们获取最终JSON有效载荷的方式),它创建了一个包含所有公共字段的数组。因此,TranslateResource
能够提供对一些“隐藏”属性的读取访问,而不将其暴露给API。以下列出了行为示例
地址
接受第一个参数为完整行(一个称为street
的字符串,因为这是ProsperWorks的做法),或者一个包含两个地址行的数组(分别称为address
和suite
)。要在这两种格式之间切换,有一个“suite”分隔符(提示:如果suite部分已经有“suite”,则不会重复)。其他参数相当标准,例如城市、邮政编码等。
<?php $sherlock = new Address('221B Baker St. suite 2', 'London'); // same as new Address(['221B Baker St.', '2'], 'London'); // same as new Address(['221B Baker St.', 'suite 2'], 'London'); echo $sherlock->street; //'221B Baker St. suite 2' echo $sherlock->address; //'221B Baker St.' echo $sherlock->suite; //'2' $nemo = new Address('42 Wallaby Way', 'Sydney'); echo $nemo->street; //'42 Wallaby Way' echo $nemo->address; //'42 Wallaby Way' echo $nemo->suite; //null //and then, use at will: CRM::person()->create([ 'name' => 'P. Sherman', 'address' => $nemo ]);
关系
这之前已经展示过:它只是一个简单的用于id
和type
的容器,没有其他功能。
自定义字段
这是最大的家伙。它负责将裸露的自定义字段规范(id + value,对人类来说并不真正有意义)翻译成可读信息。原始字段ID(相同字段具有相同的ID,即使跨越不同类型的资源)存储在custom_field_definition_id
中,与value
一起。始终存在一个只读的name
属性,包含字段的实际名称,如果它是列表,一个只读的字符串valueName
也将填充字段的可读值。
要创建自定义字段条目,您可以使用字段ID或字段名作为第一个参数,以及值ID或字符串作为第二个参数。但是,当您使用字符串而不是ID时,请务必检查大小写!
以下示例显示了如何使用类以及它如何在SDK响应中返回
<?php $person = CRM::person()->create([ 'name' => 'John Doe', 'custom_fields' => [ new CustomField('Alias', 'Johnny Doe') ] ]); print_r($person); // ProsperWorks\Resources\BareResource Object ( // [id] => 12340904 // [name] => John Doe // [custom_fields] => Array ( // [Alias] => ProsperWorks\SubResources\CustomField Object ( // [custom_field_definition_id] => 128903 // [value] => Johnny Doe // [name:protected] => Alias // [valueName:protected] => // [...] // ) // ) // [custom_fields_raw] => Array ( // [0] => stdClass Object ( // [custom_field_definition_id] => 128903 // [value] => Johnny Doe // ) // [1] => stdClass Object ( // [custom_field_definition_id] => 124953 // [value] => // ) // ) // [...] // )
“分类”子资源
所有这些类都继承自Categorized
,使它们具有category
属性和一些用作值的常量。如果在子类中,某个常量被标记为“已弃用”,则表示它实际上与该对象不兼容。
它们的签名相同:值作为第一个参数,类别作为第二个参数。
电子邮件
最简单的子类:具有email
属性和category
。
电话
第一个参数是字符串number
。但这里的技巧是,您可以使用类似“123-4444 x123”的方式提供扩展,它将被分割成两个只读字段:simpleNumber
和extension
。
URL
将有效的url
作为第一个参数,如果存在匹配,将自动填充社交URLcategory
。在其他情况下,您可以像往常一样手动将类别作为第二个参数提供。
Webhooks
另一方面,如果您想从ProsperWorks获取更新,您必须设置自己的端点,以便在发生任何更改时调用它们。
提示:您可能想查看[Webhooks指南];它仍在开发中,这就是为什么它仍然是一篇知识库文章的原因。
可用事件
根据文档,您可以订阅三种类型的事件
Webhooks::EV_NEW
- 创建了一个新条目
Webhooks::EV_UPDATE
- 一个条目被更新
Webhooks::EV_DELETE
- 一个条目被删除
Webhooks::EV_ALL
- 捕获所有事件的通配符常量,它将您订阅到所有事件
这些对于任何主要端点都可用,列在Webhooks::ENDPOINTS
下
- 公司
- 潜在客户
- 机会
- 人员
- 项目
- 任务
如何与webhooks交互
在Webhooks
类中有一组方法,您应该在配置它们时在项目的REPL或CLI工具上使用,或在Controller中操作ProsperWorks调用
要配置webhooks
首先,您必须使用应用程序环境的根路径实例化Webhooks
对象,否则它将使用Config
配置的默认设置(如果有的话)。这种就地配置能力在测试过程中非常有用。
然后,您可以使用几种方法与ProsperWorks Webhooks API交互
list(int $id = null)
- 返回按ID索引的webhook详细信息列表。
create(string $endpoint, $event = self::ALL)
- 为新端点和事件匹配订阅一个新的webhook,在`Config`中指定了密钥。您应该使用CRM::RES_*常量之一作为第一个参数。
delete(int ...$id)
- 从ProsperWorks池中删除一个或多个webhook。
解释webhook调用
每当在设置的端点上发生某个事件时,ProsperWorks的服务器将向以下地址发起HTTPS调用:<root_path>/prosperworks_hooks/<endpoint>/<event>
。您的控制器应该能够以最适合您应用程序的方式解释这一点,但大部分信息也重复在负载中,所以您可以自由地有一个通用的捕获操作来处理它。
webhook负载
负载简单明了。它包含受影响/生成的ID(通常是单个ID,除非是批处理操作),受影响端点和生成的事件,webhook订阅ID,时间戳和我们的密钥。
在执行任何进一步操作之前,出于安全考虑,您应该验证密钥是否正确。为此,以数组格式调用validateSecret()
,它会搜索密钥,解密并验证它。如果密钥不存在,则返回null;如果存在但无效,则返回false。
下一步通常是您像往常一样访问ProsperWorks API,收集有关创建/更新的资源的详细信息,并相应地更新您的数据库,或者删除需要删除的内容。
值得注意的是,在他们的当前UI中,用户所做的每个字段更改都会自动保存;这对他们的用户来说很好,但每个保存调用都会生成一个新的webhook调用,这可能会使您的服务器不堪重负。
因此,一个不错的想法是有一个某种类型的队列系统来处理这些更改,也许甚至有一个deboncer可以减少对同一资源的重复更改 - 例如,丢弃在短时间内发生相同端点、事件和ID的负载。
如何使用webhook进行开发
这里的一个重要事实是ProsperWorks要求所有webhook调用都进行加密,这意味着您必须为它们提供HTTPS地址。除此之外,您的web服务器必须在互联网上公开可访问。这对开发环境来说可能不是最简单设置,对吧?
好吧,建议在开发过程中使用ngrok。您可以从命令行轻松运行它,并让它打开稳定的HTTPS隧道,并提供它的URL;您将其输入到Webhooks
对象中,并设置新的订阅,这样当您在他们的UI上做出更改时,ProsperWorks可以调用它。
也可以使用Ngrok检查器来检查HTTP流量;运行时也会显示其URL。这可能会很有用,因为您不必编辑字段上千次来验证代码是否工作,因为它能够存储并重复接收到的调用。很酷,不是吗?