igorsantos07/prosperworks

此包已被放弃,不再维护。没有建议的替代包。

ProsperWorks CRM API的非官方(但相当不错)SDK

1.0.18 2018-04-03 15:25 UTC

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通信。

  1. 登录ProsperWorks,转到设置 > 偏好 / API密钥。在那里,您可以为登录用户生成一个新的API密钥。复制该密钥以及用户电子邮件。

  2. 创建一个引导文件,或者将以下代码包含在项目配置部分的以下代码中:\ProsperWorks\Config::set($email, $token)

Webhooks参数

(可选)如果您打算使用Webhooks从ProsperWorks获取更新,您还需要在该方法中输入三个更多参数

  1. Webhooks密钥,将用于防止意外调用您的路由。这应该是一个纯文本字符串。
  2. 根URL。这可能是您用于系统的相同域名/路径,ProsperWorks将向其POST。有关Webhooks部分的更多信息。
  3. 加密对象。它应该响应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
除了上面提到的所有功能外,还包括完整的请求有效负载。
nullfalse0ProsperWorks\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的做法),或者一个包含两个地址行的数组(分别称为addresssuite)。要在这两种格式之间切换,有一个“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
]);

关系

这之前已经展示过:它只是一个简单的用于idtype的容器,没有其他功能。

自定义字段

这是最大的家伙。它负责将裸露的自定义字段规范(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”的方式提供扩展,它将被分割成两个只读字段:simpleNumberextension

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。这可能会很有用,因为您不必编辑字段上千次来验证代码是否工作,因为它能够存储并重复接收到的调用。很酷,不是吗?