smith-carson/prosperworks-sdk

该软件包已被弃用,不再维护。未建议替代软件包。

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

1.0.18 2018-04-03 15:25 UTC

This package is auto-updated.

Last update: 2021-07-01 15:39:14 UTC


README

布局
默认

介绍

在实施时,没有ProsperWorks的SDK,我们需要执行大量操作来从我们的旧CRM迁移数据并与其他子系统同步信息,因此我们设计了一些类来封装API操作。它使用Guzzle,但最终变成了一个独立的库。

该项目最初由 igorsantos07 编写,现在由 smith-carson 维护(网站)。

安装

1. 在您的项目中安装

您需要运行 PHP 7 (是的,自2015年12月以来它已经稳定,并包含许多有用的功能,现在就升级吧!)

要使用Composer将其添加到您的项目中,请运行 composer require igorsantos07/prosperworks。如果您的 min-requirements 是 "stable",则它将获取最稳定的版本;否则为 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 的对象。

它主要用于缓存(一个小时)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 的内部组件。

在设置好配置后,ProsperWorks API 调用通过简单的流畅 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索引的ID + 类型(或Relation辅助对象)的列表来实现。

<?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,...)将从UNIX时间戳转换为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 作为第一个参数,如果存在匹配的社会 URL category,则将自动填充。对于其他情况,您可以根据惯例手动提供类别作为第二个参数。

Webhooks

另一方面,如果您想从 ProsperWorks 获取更新,您必须设置 您自己的 端点,它们可以调用以获取任何更改。

提示:您可能想查看[Webhooks 指南];它仍在开发中——这就是为什么它仍然是一篇知识库文章。

可用事件

根据文档,您可以订阅三种类型的事件

Webhooks::EV_NEW
创建了一个新条目
Webhooks::EV_UPDATE
一个条目被更新
Webhooks::EV_DELETE
一个条目被删除
Webhooks::EV_ALL
捕获所有事件的常量,将一次订阅所有事件

以下内容适用于所有主要端点,列在Webhooks::ENDPOINTS

  • 公司
  • 线索
  • 机会
  • 个人
  • 项目
  • 任务

如何与Webhook交互

在配置Webhook时,您应该在项目的REPL或CLI工具中使用Webhooks类上的几个方法,或者在自己的控制器中操作ProsperWorks调用

配置Webhook

首先,您必须使用应用程序环境的根路径实例化Webhooks对象,否则它将使用由Config配置的默认值(如果有)。在测试期间,这种就地配置能力将很有用。

然后,您可以使用一些方法与ProsperWorks Webhook 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>。您的控制器应该以最适合您应用程序的方式解释这一点,但大部分信息也重复在负载中,因此您可以有一个单个的catch-all操作来处理它。

Webhook负载

负载简单明了。它包含受影响/生成的ID(通常只有一个,除非是批量操作),受影响端点和生成事件,Webhook订阅ID,时间戳和我们的密钥。

在执行任何其他操作之前,出于安全考虑,您应该验证密钥是否正确。为此,以数组格式调用带有负载的validateSecret()。它将搜索密钥,解密并验证它。如果密钥不存在,则返回null;如果存在但无效,则返回false。

下一步通常是通过常规方式访问ProsperWorks API来收集有关创建/更新资源的详细信息,并相应地更新您的数据库,或删除需要删除的内容。

值得一提的是,根据他们当前的UI,用户所做的每个字段更改都会自动保存;这对他们的用户来说很好,但每个保存调用都会产生一个新的Webhook调用,这可能会使您的服务器不堪重负。
因此,一个很好的想法是有一个某种形式的队列系统来处理这些更改,甚至可能有一个debouncer可以减少对同一资源的重复更改 - 即在短时间内忽略具有相同端点、事件和ID的负载。

如何使用Webhook进行开发

这里一个重要的事实是,ProsperWorks要求所有webhook调用都必须加密,这意味着你必须为它们提供一个HTTPS地址。除此之外,你的web服务器必须公开地可供网络访问。这对于开发环境来说可能不是最简单易用的设置,对吧?

嗯,建议在开发过程中使用ngrok。你可以很方便地从命令行运行它,并让它打开一个稳定的HTTPS隧道,并给你它的URL;你将这个URL输入到Webhooks对象中,并设置新的订阅,当你在ProsperWorks的UI上做出更改时,ProsperWorks可以调用这些订阅。
使用Ngrok检查器还可以检查HTTP流量;当你运行它时,它的URL也会显示出来。这可能很有用,因为你不需要编辑字段千百次来验证你的代码是否工作,因为它能够存储和重复收到的调用。很酷,不是吗?