uncgits/canvas-api-php-library

用于与 UNCG 的 Canvas API 交互的 PHP 库

1.1.2 2024-06-27 14:57 UTC

This package is auto-updated.

Last update: 2024-09-27 15:25:15 UTC


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

  1. 根据您要执行的调用类型,在 Uncgits\CanvasApi\Clients 中选择您想要的客户端。

  2. 然后,选择您想要用于 HTTP 交互的适配器(在 Uncgits\CanvasApi\Adapters 中找到)。

默认情况下,此软件包提供了可用的 Guzzle 适配器(您可以编写自己的;稍后会有更多介绍)。

  1. 最后,选择您想要使用的配置。

  2. 实例化 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)步

  1. 第一个API调用将“通知”Canvas你将上传一个文件,并告诉它文件将是什么样子(文件类型、期望的文件名、大小等)
  2. Canvas将返回一个URL,文件本身将发送到该URL,第二个API调用将通过multipart表单请求实际发送数据
  3. 如果请求结果为3xx响应代码,则需要调用第三个API来“完成”事务。

使用本库,你可以通过以下方式上传文件

  • Files客户端中调用uploadFile()方法,并提供namesizecontent_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

  • 第一个标记版本
  • 完全支持以下操作集
    • 账户报告
    • 账户
    • 分析
    • 作业
    • 注册
    • 注册学期
    • 测验提交
    • 测验
    • 角色
    • 章节
    • 标签页
  • 几乎完全支持以下操作集(尚未包括文件上传)
    • 课程
    • 用户