使用第三方 APIs

关于 REST API 的理论已经讲得够多了;现在是时候深入研究一个现实世界的例子了。在本节中,我们将编写一个小型 PHP 应用程序,与 Twitter 的 REST API 进行交互,包括请求开发人员凭证、验证和发送请求。我们的目标是让你第一次体验使用 REST API,并向你展示它比你想象的要容易得多。它还将帮助你更好地理解这些 API 的工作原理,以便以后更容易地构建自己的 API。

获取应用程序的凭据

REST API 通常有应用程序的概念。应用程序就像是开发网站上的一个账户,用于识别使用 API 的用户。用于访问 API 的凭据将与此应用程序相关联,这意味着您可以将多个应用程序链接到同一个账户。

假设您有一个 Twitter 帐户,请访问 https://apps.twitter.com 以创建一个新的应用程序。单击 "创建新应用程序 "按钮,进入应用程序详细信息表单。字段非常简单,只需输入应用程序名称、描述和网站 URL 即可。这里不需要回调 URL,因为它只用于需要访问他人账户的应用程序。同意条款和条件才能继续。

重定向到应用程序页面后,您将看到各种可以编辑的信息。由于这只是一个示例,让我们直接进入最重要的部分:证书。点击 "密钥和访问令牌" 选项卡,查看消费者密钥(API 密钥)和消费者秘密(API 秘密)的值。这里没有其他我们需要的东西。你可以将它们保存在你的文件系统中,例如 ~/.twitter_php7.json:

{
    "key": "iTh4Mzl0EAPn9HAm98hEhAmVEXS",
    "secret": "PfoWM9yq4Bh6rGbzzJhr893j4r4sMIAeVRaPMYbkDer5N6F"
}
确保您的证书安全

应认真对待 REST API 凭据的安全问题。事实上,您应该保护所有类型的凭据,比如数据库凭据。但不同的是,你通常会在自己的服务器上托管数据库,这就给想要攻击你的人增加了一些难度。另一方面,第三方 REST API 并不属于你的系统,拥有你的凭据的人可以代表你自由使用你的账户。

切勿在代码库中包含您的凭据,尤其是在 GitHub 或其他存储库中有代码的情况下。一种解决方案是在您的服务器中,在您的代码之外,有一个包含凭据的文件;如果该文件是加密的,那就更好了。并尝试定期刷新凭据,您可能可以在提供商的网站上做到这一点。

设置应用程序

我们的应用程序将非常简单。它将由一个允许我们获取推文的类组成。这将由我们的 app.php 脚本管理。

由于我们必须进行 HTTP 请求,因此我们可以编写自己的函数来使用 cURL(一组 PHP 本地函数),或者使用著名的 PHP 库 Guzzle。该库可在 Packagist 中找到,因此我们将使用 Composer 将其包含在内:

$ composer require guzzlehttp/guzzle

我们将创建一个 Twitter 类,从构造函数中获取证书,并创建一个公共方法:fetchTwits。现在,我们只需创建一个骨架,这样就可以使用它;我们将在后面的章节中实现这些方法。在 src/Twitter.php 中添加以下代码:

<?php

namespace TwitterApp;

class Twitter {

    private $key;
    private $secret;

    public function __construct(String $key, String $secret) {
        $this->key = $key;
        $this->secret = $secret;
    }

    public function fetchTwits(string name, int $count): array {
        return [];
    }
}

由于我们设置了 TwitterApp 命名空间,因此需要更新 composer.json 文件,添加以下内容。记得运行 composer update 更新自动加载器。

"autoload": {
  "psr-4": {"TwitterApp\\": "src"}
}

最后,我们将创建一个基本的 app.php 文件,其中包括 Composer 自动加载器、读取凭据文件并创建 Twitter 实例:

<?php

use TwitterApp\Twitter;

require __DIR__ . '/vendor/autoload.php';

$path = $_SERVER['HOME'] . '/.twitter_php7.json';
$jsonCredentials = file_get_contents($path);
$credentials = json_decode($jsonCredentials, true);

$twitter = new Twitter($credentials['key'], $credentials['secret']);

请求访问令牌

在实际应用中,您可能希望将与身份验证相关的代码与处理获取或发布数据等操作的代码分开。为了保持简单,我们将让 Twitter 类自己知道如何进行身份验证。

首先,我们将在类中添加一个 $client 属性,该属性将包含 Guzzle 客户端类的一个实例。该实例将包含 Twitter API 的基本 URI,我们可以将其作为常量 TWITTER_API_BASE_URI。在构造函数中实例化该属性,以便其余方法可以使用它。还可以添加一个 $accessToken 属性,其中包含 Twitter API 在验证时返回的访问令牌。此处突出显示了所有这些更改:

<?php

namespace TwitterApp;

use Exception;
use GuzzleHttp\Client;

class Twitter {
    const TWITTER_API_BASE_URI = 'https://api.twitter.com';
    private $key;
    private $secret;
    private $accessToken;
    private $client;

    public function __construct(String $key, String $secret) {
        $this->key = $key;
        $this->secret = $secret;

        $this->client = new Client(
            ['base_uri' => self::TWITTER_API_BASE_URI]
        );
    }

    //...
}

下一步是编写一个方法,在提供密钥和秘密的情况下,向提供者申请访问令牌。更具体地说:

  • : 连接密钥和秘密。使用 Base64 对结果进行编码。

  • /oauth2/token 发送 POST 请求,并将编码后的凭据作为 Authorization 头。同时包含一个 Content-Type 标头和一个正文(更多信息请查看代码)。

现在我们调用 Guzzle 客户端实例的 post 方法,发送两个参数:端点字符串(/oauth2/token)和一个包含选项的数组。这些选项包括请求的标题和正文,稍后您将看到。该调用的响应是一个标识 HTTP 响应的对象。您可以使用 getBody 提取响应的内容(正文)。Twitter 的 API 响应是一个包含一些参数的 JSON 文件。其中你最关心的是 access_token,即你需要在随后的每次 API 请求中包含的令牌。提取并保存它。完整的方法如下:

private function requestAccessToken() {
    $encodedString = base64_encode(
        $this->key . ':' . $this->secret
    );
    $headers = [
        'Authorization' => 'Basic ' . $encodedString,
        'Content-Type' => 'application/x-www-formurlencoded;charset=UTF-8'
    ];
    $options = [
        'headers' => $headers,
        'body' => 'grant_type=client_credentials'
    ];

    $response = $this->client->post(self:: OAUTH_ENDPOINT, $options);
    $body = json_decode($response->getBody(), true);

    $this->accessToken = $body['access_token'];
}

在构造函数的末尾添加这两行代码,就可以试用了:

$this->requestAccessToken();
var_dump($this->accessToken);

运行应用程序,使用以下命令查看提供程序给出的访问令牌。记住删除前面两行,以便继续本节内容。

$ php app.php

请记住,尽管所有 OAuth 身份验证的密钥和秘密以及访问令牌都是一样的,但具体的编码方式、使用的端点以及从提供者收到的响应都是 Twitter API 独有的。可能有其他几种方法完全相同,但一定要查看每种方法的文档。

获取推文

最后,我们将进入实际使用 API 的部分。我们将实现 fetchTwits 方法,以获取给定用户最近 N 条推文的列表。为了执行请求,我们需要在每个请求中添加授权头(Authorization header),这次要加上访问令牌(access token)。由于我们想让这个类尽可能可重用,所以我们将其提取为一个私有方法:

private function getAccessTokenHeaders(): array {
    if (empty($this->accessToken)) {
        $this->requestAccessToken();
    }

    return ['Authorization' => 'Bearer ' . $this->accessToken];
}

正如你所看到的,前面的方法还允许我们从提供者那里获取访问令牌。这一点非常有用,因为如果我们提出多个请求,我们只需请求一次访问令牌,而且我们有一个唯一的地方可以这样做。现在添加以下方法的实现:

const GET_TWITS = '/1.1/statuses/user_timeline.json';
//...
public function fetchTwits(string $name, int $count): array {
    $options = [
        'headers' => $this->getAccessTokenHeaders(),
        'query' => [
            'count' => $count,
            'screen_name' => $name
        ]
    ];

    $response = $this->client->get(self::GET_TWITS, $options);
    $responseTwits = json_decode($response->getBody(), true);

    $twits = [];
    foreach ($responseTwits as $twit) {
        $twits[] = [
            'created_at' => $twit['created_at'],
            'text' => $twit['text'],
            'user' => $twit['user']['name']
        ];
    }

    return $twits;
}

前述方法的第一部分使用访问标记头和查询字符串参数(本例中使用要检索的推文数量和用户)构建选项数组。我们执行 GET 请求,并将 JSON 响应解码为一个数组。该数组包含大量我们可能不需要的信息,因此我们对其进行遍历,以提取我们真正需要的字段—​在本例中就是日期、文本和用户。

要测试应用程序,只需调用 app.php 文件末尾的 fetchTwits 方法,指定你关注的某个人或你自己的 Twitter ID。

$twits = $twitter->fetchTwits('neiltyson', 10);
var_dump($twits);

您应该会得到与我们类似的响应,如以下屏幕截图所示:

image 2023 11 04 12 23 52 838

需要注意的一点是,访问令牌会在一段时间后过期,并返回带有 4xx 状态代码(通常为 401 未授权)的 HTTP 响应。当状态代码为 4xx 或 5xx 时,Guzzle 会抛出异常,因此很容易管理这些情况。您可以在执行 GET 请求时添加此代码:

try {
    $response = $this->client->get(self::GET_TWITS, $options);
} catch (ClientException $e) {
    if ($e->getCode() == 401) {
        $this->requestAccessToken();
        $response = $this->client->get(self::GET_TWITS, $options);
    } else {
        throw $e;
    }
}