uncgits / canvas-api-php-library
用于与 UNCG 的 Canvas API 交互的 PHP 库
Requires
- php: ^7.0 || ^8.0
- nesbot/carbon: >=1.22.0
Suggests
- guzzlehttp/guzzle: 6.* recommended to make HTTP request with Guzzle adapter
README
本软件包是一个 PHP 库,用于与 Canvas REST API 交互。
范围
本软件包目前还不是与 Canvas API 的综合接口。如果您想为此库贡献额外的功能,非常欢迎提交拉取请求。
安装和快速入门
要安装此软件包: composer require uncgits/canvas-api-php-library
如果您正在使用内置的 Guzzle 驱动程序,您还需要确保在项目中已安装 Guzzle 6.:`composer require guzzlehttp/guzzle:"6."`。此软件包未将 Guzzle 列为依赖项,因为它不是严格必需的(计划中包括其他 HTTP 客户端的适配器,并且您始终可以编写自己的适配器)。
使用它!
创建一个配置对象
在您的应用程序中创建一个扩展 Uncgits\CanvasApi\CanvasApiConfig
的类。然后,在 __construct()
中,使用 $this->setApiHost()
和 $this->setToken()
方法设置您的 API 凭据。您很可能会在这里使用环境变量,或者非提交类或文件的引用,其中包含您的凭据。示例
<?php
namespace App\CanvasConfigs;
use Uncgits\CanvasApi\CanvasApiConfig;
class TestEnvironment extends CanvasApiConfig
{
public function __construct()
{
$this->setApiHost(getenv('CANVAS_DOMAIN'));
$this->setToken(getenv('CANVAS_TOKEN'));
}
}
实例化 CanvasApi
类
根据您要执行的调用类型,在
Uncgits\CanvasApi\Clients
中选择您想要的客户端。然后,选择您想要用于 HTTP 交互的适配器(在
Uncgits\CanvasApi\Adapters
中找到)。
默认情况下,此软件包提供了可用的
Guzzle
适配器(您可以编写自己的;稍后会有更多介绍)。
最后,选择您想要使用的配置。
实例化
Uncgits\CanvasApi\CanvasApi
类,并通过构造函数数组或设置方法传递上述三个对象
// OPTION 1 - array in constructor
// instantiate a client
$accountsClient = new \Uncgits\CanvasApi\Clients\Accounts;
// instantiate an adapter
$adapter = new \Uncgits\CanvasApi\Adapters\Guzzle;
// instantiate a config
$config = new App\CanvasConfigs\TestEnvironment;
// pass as array to API class
$api = new \Uncgits\CanvasApi\CanvasApi([
'client' => $accountsClient,
'adapter' => $adapter,
'config' => $config,
]);
// OPTION 2 - use setters
// instantiate a client
$accountsClient = new \Uncgits\CanvasApi\Clients\Accounts;
// instantiate an adapter
$adapter = new \Uncgits\CanvasApi\Adapters\Guzzle;
// instantiate a config
$config = new App\CanvasConfigs\TestEnvironment;
// instantiate API class
$api = new \Uncgits\CanvasApi\CanvasApi;
$api->setClient($accountsClient);
$api->setAdapter($adapter);
$api->setConfig($config);
或者,通过上面的任一方法直接传递类名
// OPTION 1 - pass class names
use \Uncgits\CanvasApi\Clients\Accounts;
use \Uncgits\CanvasApi\Adapters\Guzzle;
use App\CanvasConfigs\TestEnvironment;
// instantiate the API class and pass in the array using class names
$api = new \Uncgits\CanvasApi\CanvasApi([
'client' => Accounts::class,
'adapter' => Guzzle::class,
'config' => TestEnvironment::class,
]);
// OPTION 2 - use setters
use \Uncgits\CanvasApi\Clients\Accounts;
use \Uncgits\CanvasApi\Adapters\Guzzle;
use App\CanvasConfigs\TestEnvironment;
$api = new \Uncgits\CanvasApi\CanvasApi([
'client' => Accounts::class,
'adapter' => Guzzle::class,
'config' => TestEnvironment::class,
]);
然后,一旦您有了客户端,如果您想要列出所有账户
// make the call
$result = $accountsClient->listAccounts(); // methods are named as they appear in Canvas API documentation
// get the contents of the result
var_dump($result->getContent()); // you receive a CanvasApiResult object
客户端流畅快捷方式
您可能希望在保持相同适配器和配置的同时使用不同的客户端。您可以通过在 API 类上使用 setClient()
永久地做到这一点,或者您可以通过流畅的 using()
可链式方法在单个事务中做到这一点。
using()
可以接受一个完整的类名(带命名空间)或内置客户端类的简化类名,例如 'users' 或 'quizsubmissions'。
use Uncgits\CanvasApi\Clients\Users;
use \Uncgits\CanvasApi\Clients\Accounts;
use \Uncgits\CanvasApi\Adapters\Guzzle;
use App\CanvasConfigs\TestEnvironment;
$api = new \Uncgits\CanvasApi\CanvasApi([
'client' => Accounts::class,
'adapter' => Guzzle::class,
'config' => TestEnvironment::class,
]);
// API class will use Accounts client.
$api->listAccounts();
// use explicit setter - now API class will use Users client for all future calls
$api->setClient(Users::class);
$api->showUserDetails();
$api->getUserSettings(); // still Users client
// OR, use fluent setter with explicit class
$api->using(Users::class)->showUserDetails();
$api->listAccounts(); // back to original Accounts Client
// OR, fluent setter with implied class (from default library)
$api->using('users')->showUserDetails();
$api->listAccounts(); // back to Accounts Client
设置参数
某些 API 调用需要额外的参数,并且有两种不同的方式可以传递它们。您选择的方式与以下逻辑直接相关
如果参数属于 URL,则它将是方法调用的参数。例如:`getSingleAccount()` 调用 `api/v1/accounts/:id`,其中 `:id` 是您要获取的账户的 ID。因此,方法的签名是 `getSingleAccount($id)`,您会在这里传递您的账户值。
如果参数不包含在 URL 中,则需要将其设置为正文参数。这包括所有必需参数以及可选参数。例如:`listAccounts()` 不需要任何参数,但您可以提供可选的 `include` 参数。像这样在客户端对象上使用可链式
addParameters()
方法
$includes = [
'includes' => [
'lti_guid',
'registration_settings',
'services'
],
];
$result = $accountsClient->addParameters($includes)->listAccounts();
请参考 API 文档以了解接受的参数以及参数的结构(特别是对于多级嵌套参数)。此库试图在某种程度上验证参数,但截至初始发布,无法验证某些深层嵌套的参数。
Laravel 包装器
本包包含一个为Laravel定制的包装包,它包括容器绑定、门面、配置文件、适配器和其它实用工具,以便您能够轻松地在Laravel应用程序中使用本包。
详细用法
架构
总体架构
本库由许多客户端组成,每个客户端都与Canvas API的一个特定类别/区域进行交互。例如,有一个测验客户端、账户客户端、用户客户端等等。每个客户端类完全基于Canvas API文档中的架构。所有客户端类都实现了Uncgits\CanvasApi\Clients\CanvasApiClientInterface
接口,该接口目前是一个空接口,但有助于识别每个客户端作为“有效”的客户端(因此您可以创建自己的)。客户端的任务是返回一个可以解析为CanvasApiEndpoint
对象的数组,该对象需要一个端点字符串、HTTP方法字符串以及调用该Canvas API方法所需的可选参数数组。
适配器类基本上是抽象的HTTP请求处理器。它们负责构建和对Canvas进行API调用,处理必要的分页,并以简单、结构化的方式格式化响应(使用CanvasApiResult
对象)。这些适配器类实现了Uncgits\CanvasApi\Adapters\CanvasApiAdapterInterface
接口。在初次发布时,仅包含一个Guzzle适配器 - 然而,稍后将会添加更多,您也可以根据自己的PHP HTTP库编写自己的适配器。(或者直接使用cURL。没有人会评判。)从技术上讲,适配器作为客户端类上的属性存在。
配置类是配置适配器需要了解的基本信息的类,以便与Canvas API交互。本包中不包含任何具体的类 - 仅包含抽象的CanvasApiConfig
类。这种架构的目的是让您能够创建几个类来支持不同的Canvas环境 - 即使您只与一个Canvas域交互,该域也有一个test
和一个beta
实例。
主API类本质上是一个“交通控制器”类,它负责确保各个组件(客户端、适配器、配置)知道执行API调用所需的所有信息。这个类之所以存在,是为了可以根据需要进行扩展。基本上,当请求进行调用时,API类应该了解执行该API调用所需的所有信息,包括正在使用的客户端、适配器和配置、端点和方法等等,这使得利用这个类进行信息用途(如日志记录或缓存...请参阅Laravel包装包的示例)变得容易。
命名
每个客户端的方法都尽可能地使用API文档中指定的名称。例如,账户客户端包含listAccounts()
、listAccountsForCourseAdmins()
等等 - 都与官方文档一致。值得注意的是,对于这条规则的一个修改:“a”/“an”/“the”总是被省略,所以我们有getSingleAccount()
、getTermsOfService()
等等。
别名
在谨慎的情况下,当Canvas API不总是遵循逻辑的RESTful语义语法时,会采用别名处理。例如,虽然getSingleAccount()
(如Canvas API文档中所述)具有描述性,但“single”修饰符与传统RESTful事务的命名方式不一致,因此可能被认为是多余的;换句话说,许多开发者可能会猜测这个应该是getAccount()
。因此,在这些情况下,虽然原始核心方法将始终遵循官方文档中的名称,但许多地方都添加了别名(如本例所示),以方便使用。这些别名只是调用“真正的”方法并返回其结果。
作为用户
如果您使用此库与包含“作为”权限(以前称为“伪装”为)其他用户的令牌,您可以在进行调用时使用链式asUser()
方法
use Uncgits\CanvasApi\CanvasApi;
$api = new CanvasApi([
'config' = new \App\CanvasConfigs\TestEnvironment::class;
'adapter' = new \Uncgits\CanvasApi\Adapters\Guzzle::class;
'client' = new \Uncgits\CanvasApi\Clients\Users::class;
]);
$result = $api->asUser(12345)->listCourseNicknames();
SIS IDs
Canvas REST API允许您用SIS ID替代许多项目的内部Canvas ID。此库支持SIS ID!
传递给所有方法的参数将直接插入到被调用的URL中。因此,您不必调用$userClient->getUserSettings(12345)
,而是可以调用$userClient->getUserSettings('sis_login_id:jsmith')
,它将正常工作。请参阅上面的页面,了解SIS ID可以和不能替代的地方,以及如何处理所需的编码和转义。
分页
分页在必要时自动在适配器级别进行处理。每次API调用都会读取返回的标头,解析并提取Link标头,遵循文档中的说明。
要自定义任何API调用上的分页,您可以使用addParameters()
设置per_page
值为您选择的任何值,或者使用内置的辅助方法setPerPage()
。
$result = $api->addParameters(['per_page' => 100])->listAccounts();
// OR
$result = $api->setPerPage(100)->listAccounts();
由于分页标头在每次API调用时都会检查,因此每个客户端事务可能需要发出几次API调用才能完成。下面将详细介绍结果是如何呈现的。
处理结果
每次客户端事务请求时,此库将封装重要信息,并以Uncgits\CanvasApi\CanvasApiResult
类的形式呈现。在该类中,您可以访问关于该事务期间进行的每个API调用的结构化信息,以及应该作为数组迭代的聚合结果集。例如,如果您请求所有帐户,并且需要5次API调用、每页10个项目来检索它们,则结果内容将是一个包含所有帐户的单个数组;这是为了节省您自己解析它们的时间!
$result = $api->listAccounts();
// get the result content - your Account objects
$accounts = $result->getContent();
// get a list of the API calls made
$apiCalls = $result->getCalls();
// get the first API call made
$firstCall = $result->getFirstCall();
// get the last API call made
$lastCall = $result->getLastCall();
// get the aggregate status code (success or failure) based on the result of all calls
$status = $result->getStatus();
// get the aggregate message (helpful if there were failures) based on the result of all calls
$status = $result->getMessage();
API调用数组
每个API调用(从CanvasApiResult
对象的$calls
数组中检索)由一些可能对您处理诸如限流(见下文)或其他调用元信息有用的关键信息组成。API调用数组存储关于请求(发送的内容)和响应(接收的内容)的信息,并按以下结构组织
[
'request' => [
'endpoint' => $endpoint, // the final assembled endpoint
'method' => $method, // get, post, put, delete
'headers' => $requestOptions['headers'], // all headers passed - includes bearer info
'proxy' => $this->config->getProxy(), // proxy host and port, if using
'parameters' => $this->parameters, // any parameters used by the client
],
'response' => [
'headers' => $response->getHeaders(), // raw headers
'pagination' => $this->parse_http_rels($response->getHeaders()), // parsed pagination information
'code' => $response->getStatusCode(), // 200, 403, etc.
'reason' => $response->getReasonPhrase(), // OK, Forbidden, etc.
'runtime' => $response->getHeader('X-Runtime') ?? '', // convenience item for length of time in seconds the call ran
'cost' => $response->getHeader('X-Request-Cost') ?? '', // convenience item for "cost" of call toward rate limit
'rate-limit-remaining' => $response->getHeader('X-Rate-Limit-Remaining') ?? '', // convenience item for rate limit bucket level remaining
'body' => json_decode($response->getBody()->getContents()) // raw body content of the response
],
]
速率限制/限流
此库不考虑速率限制、限流或自动重试/指数退避。大多数时候,这些问题只有在运行会引发同时调用API的脚本时才会遇到,并且在通过大量数据集进行分页时不会成为问题。根据Canvas API文档
由于请求的成本大致基于处理所需的时间,并且配额(默认情况下)的补充速度比实时快,因此任何不超过一个同时请求的API客户端不太可能被限流。
这个库旨在作为Canvas REST API的接口;因此,它不需要关心在客户端应用中的使用方式。你应该在应用程序代码中注意这一点 - 如果达到速率限制,你可以期待这个库通过单个API调用的响应信息(403状态码,“速率限制已超过”消息)告诉你。
为了方便,速率限制信息包含在每个API调用结果的基本级别数组中(见上文),这样你就不需要自己解析头信息。
限制结果集大小(最大结果数)
默认情况下,该软件包包含一个相对合理的默认值,每个事务最多9999个结果。一般来说,如果你一次需要比这更多的记录,可能存在更好的方法来获取它们(通过搜索查询参数等),而不是进行100+分页的API调用。然而,你可以根据自己的想象力(以及系统内存和速率限制)覆盖此限制。
$config->setMaxResults(250);
// OR...
$api->using('users')->setMaxResults(250)->listUserPageViews('jdoe1'); // calling this on the API class will trickle through to the config class (assuming it is set)
使用代理
如果你需要使用HTTP代理,请在CanvasApiConfig
对象中使用setProxyHost()
、setProxyPort()
和useProxy()
设置。
传递额外的头信息
如果你需要在请求中设置额外的头信息,你可以利用客户端类上的setAdditionalHeaders()
方法,它接受一个键值对数组。
文件上传(multipart数据)
本库支持按照Canvas文件上传文档中所述的POST方法上传。此过程分为2(或3)步
- 第一个API调用将“通知”Canvas你将上传一个文件,并告诉它文件将是什么样子(文件类型、期望的文件名、大小等)
- Canvas将返回一个URL,文件本身将发送到该URL,第二个API调用将通过multipart表单请求实际发送数据
- 如果请求结果为
3xx
响应代码,则需要调用第三个API来“完成”事务。
使用本库,你可以通过以下方式上传文件
- 在
Files
客户端中调用uploadFile()
方法,并提供name
、size
和content_type
参数。 - 检查结果以获取文件的新的POST URL以及
upload_params
。 - 从
upload_params
形成有效的multipart数组,并附加file
。以下是一个使用包含的Guzzle适配器的有效数组示例
[
[
'name' => 'filename',
'contents' => 'myfile.jpg',
],
[
'name' => 'content_type',
'contents' => 'image/jpeg',
],
[
'name' => 'file',
'contents' => fopen('/path/to/file', 'r'),
],
]
将multipart数据添加到请求中,并使用提供的URL调用
uploadFileToUploadUrl()
。如果你收到
3xx
响应代码,根据Location头信息进行GET请求以完成事务。你可以使用Base
客户端来完成此操作。
这个过程可能如下所示
use Uncgits\CanvasApi\CanvasApi;
$api = new CanvasApi([
'config' = new \App\CanvasConfigs\TestEnvironment::class;
'adapter' = new \Uncgits\CanvasApi\Adapters\Guzzle::class;
'client' = new \Uncgits\CanvasApi\Clients\Files::class;
]);
$result = $api->addParameters(['name' => 'myfile.jpg', 'size' => '100000', 'content_type' => 'image/jpeg']->uploadFile($folderId); // you may need to act-as the user here too.
// parse out the data
$url = $result->getContent()->upload_url;
$multipartArray = (array) $result->getContent()->upload_params;
$multipartArray['file'] = fopen('/path/to/file.jpg', 'r');
// create a valid multipart array for Guzzle
$multipartArray = array_map(function ($value, $key) {
return ['name' => $key, 'contents' => $value];
}, $multipartArray, array_keys($multipartArray));
// upload the file
$uploadResult = $api->addMultipart($multipartArray)->uploadFileToUploadUrl($url);
// finalize if needed
$code = $uploadResult->getLastResult()['code'];
if ($code >= 300 && $code < 400) {
$location = $uploadResult->getLastCall()['headers']['Location'];
$finalizeResult = $api->using('base')->makeCallToRawUrl($location, 'get');
}
编写自己的适配器
适配器负责与Canvas API实际交互的所有内容。适配器的职责包括
- 组装调用端点、头信息、参数、主体和其他选项
- 执行适当的HTTP请求
- 解析响应
- 读取分页头信息并确定是否需要另一个调用以完成请求的事务
- 报告错误
- 将结果汇总到一个数组中
- 记录事务中所有调用的内容。
《CanvasApiAdapterInterface》接口展示了适配器的实现方式。该接口中的大多数基本方法(设置器、获取器、便捷别名等)已在《ExecutesApiCalls》特性中为您实现,因此一般来说,您应该首先使用该特性。(当然,如果您愿意,也可以覆盖特性中的方法。)
因此,在每个适配器中,剩下的工作就是您自己实现以下方法
call()
transaction()
parsePagination()
normalizeResult()
编写自己的客户端
所有客户端都是《CanvasApiClientInterface》类的实现,并且被显式地传递给API对象——因此编写自己的客户端类是可能的。目前,该接口类不需要具体的实现...您随时可以开始编写自定义客户端!
理论上,这个API库最终会完成,因此编写自定义客户端可能不再是必要的,但随着这个库仍在不断发展,可能存在一些API功能缺失,您可能需要自己填补这些空白。如果您这样做,请考虑创建一个拉取请求,将您的适配器添加到这个仓库中!
扩展API类
如果您需要额外的功能,例如日志记录或缓存,您可以编写自己的《CanvasApi》包装类,该类扩展了《CanvasApi》类。最常见的使用场景可能是为Config和Adapter类设置一些默认值,也许在构造函数中,或者覆盖《execute()`》方法以在事务启动前或完成后执行额外的操作。
贡献
请随时提交拉取请求到这个包,以帮助提高其对Canvas API的覆盖范围,以及添加适配器或修复问题。这个包最初是为了满足UNC Greensboro在Canvas集成方面的紧急需求而构建的,因此还不是全面的。
许可证
有关许可证权利和限制,请参阅LICENSE文件(BSD)。
有疑问吗?有顾虑吗?
请使用此仓库的问题跟踪器来报告错误。对于安全问题,请直接联系its-laravel-devs-l@uncg.edu,而不是使用问题跟踪器。
版本历史
1.1.2
- 仅更新README文件,因为我们忘记在Tag 1.1.1中更新它。
1.1.1
- 更新“Link”头部的格式,以符合Instructure所做的更改。
- 在Guzzle适配器中,“Link”现在是“link”。
- Instructure在这里宣布了更改:Instructure产品博客
1.1
- 修复了《Users》客户端中的参数排序问题,该问题已在PHP 8中弃用。
- 调整PHP版本约束,以便在未来的主要PHP版本发布中解决类似的问题。
1.0.3
- 在《CanvasApiResult》中添加了对'tquota'键作为单个结果处理的支持。
1.0.2
- 在《CanvasApiResult》中添加了对更多单个成员对象结果的额外处理。这可能需要进一步的审查,但现在它是有效的。
- 为安装/使用时的清晰度调整README。
1.0.1
- 将BSD许可证信息添加到《composer.json》中。
1.0
- 官方开源许可证
0.6.7
- 添加了Quiz Submission Events客户端
- 添加了Feature Flags客户端
- 添加了对检测单个功能标志的特殊结果处理
0.6.6
- 由于我们已经遵循了PSR-4声明,因此进行了更改
0.6.5
- 添加了External Tool客户端
0.6.4
- 修复了从API获取错误消息的实际错误。
0.6.3
- 修复了从API获取错误消息的错误。
0.6.2
- 修复了当只返回单个记录时最大结果的问题。
0.6.1
- 添加了对获取用户配置文件(《Users》客户端)的调用
0.6
- 添加了对最大结果(限制结果集大小)的真正支持。
0.5.2
- 调整结果解析方式,因为一些API调用返回的数据组织得很奇怪——例如,注册学期被封装在一个父对象中——所以这是一个易于发现的低垂之果方法。如果问题持续存在,我们可能需要更加明确,并为每个客户端编写结果解析类。
0.5.1
- 增加了动态调用能力,无需授权/Bearer头
- 增加了动态告知Guzzle适配器对参数进行URL编码的能力(某些POST请求需要此功能)
0.5
- 支持多部分(文件上传)。目前尚无创建多部分或其他数据的助手(helper)方法。
- 向
CanvasApiResult
类添加了getLastResult()
方法,该方法输出最后调用的代码和原因。
0.4.3
- 添加了
Groups
客户端
0.4.2
- 向
Enrollments
客户端添加了reactivateEnrollment()
方法
0.4.1
- 修复了一些客户端仍然引用
$this->setParameters()
的bug - 消除了某些依赖于
$this->setParameters()
的助手方法 - 一些拼写修正
0.4
- 又一次重写……将所有东西都集中到API类中,使其在交易发生时基本上无所不知。
- 简化了
execute()
方法,以便在包装类中轻松覆盖 - 添加了客户端
- SIS导入
- SIS导入错误
0.3
- 针对新客户端格式进行了重写
- 修复了Guzzle适配器中的分页bug,当存在其他查询参数时,分页会失败
using()
方法现在是临时的,不会覆盖API类上的原始客户端设置- 移除了设置客户端、适配器和配置的顺序要求,而是在过程中进行错误检查,以便更容易地替换组件。
0.2
- 针对新适配器格式进行了重写
0.1
- 第一个标记版本
- 完全支持以下操作集
- 账户报告
- 账户
- 分析
- 作业
- 注册
- 注册学期
- 测验提交
- 测验
- 角色
- 章节
- 标签页
- 几乎完全支持以下操作集(尚未包括文件上传)
- 课程
- 用户