zadorin/airtable-php

简单的Airtable API PHP封装库

v1.0.1 2024-05-24 11:02 UTC

This package is auto-updated.

Last update: 2024-09-24 11:54:10 UTC


README

安装

composer require zadorin/airtable-php

请注意,从v1.0.0版本开始,库需要PHP 8.2。

如果您有较低的PHP版本,请使用v0.*.*版本。

composer require zadorin/airtable-php:^0

使用

基本设置

$apiKey = 'key***********';
$database = 'app***********';
$tableName = 'my-table';

$client = new \Zadorin\Airtable\Client($apiKey, $database);

您可以在账户设置中找到API密钥,在API文档中找到数据库名称。

插入一些行

$client->table($tableName)
    ->insert([
        ['name' => 'Ivan', 'email' => 'ivan@test.tld'],
        ['name' => 'Peter', 'email' => 'peter@test.tld']
    ])
    ->execute();

获取多行

$recordset = $client->table($tableName)
    ->select('id', 'name', 'email') // you can use shortcut select('*') to fetch all columns
    ->where(['name' => 'Ivan', 'email' => 'ivan@test.tld'])
    ->orderBy(['id' => 'desc'])
    ->limit(10)
    ->execute();

var_dump($recordset->fetchAll()); // returns set of Record objects
var_dump($recordset->asArray()); // returns array of arrays

通过记录ID获取特定行

$recordset = $client->table($tableName)
    ->find('rec1*******', 'rec2*******')
    ->execute();

迭代并更新记录

while ($record = $recordset->fetch()) {
    var_dump($record->getId()); // rec**********
    var_dump($record->getFields()); // [id => 1, name => Ivan, email => ivan@test.tld]

    $record->setFields(['name' => 'Ivan the 1st']);
    $client->table($tableName)->update($record);
}

分页

$query = $client->table($tableName)
    ->select('*')
    ->orderBy(['id' => 'desc'])
    ->paginate(50); // limit(50) works the same. Default (and maximal) page size is 100

while ($recordset = $query->nextPage()) {
    var_dump($recordset->fetchAll());
}

删除行

$records = $client->table($tableName)
    ->select('id', 'email')
    ->where(['email' => 'peter@test.tld'])
    ->execute()
    ->fetchAll();

$client->delete(...$records)->execute();

复杂过滤器

您可以构建复杂的公式来过滤记录,但请注意,公式会应用于每条记录,可能会减慢查询速度。

假设我们准备了以下查询对象

$query = $client->table('my-table')->select('*');

查询构建器

以下行会得到相同的结果

$query->where(['email' => 'ivan@test.tld']);
$query->where('email', 'ivan@test.tld');
$query->where('email', '=', 'ivan@test.tld');

您可以使用不同的逻辑运算符

$query->where('email', '!=', 'ivan@test.tld');
$query->where('code', '>', 100);

您可以将多个where语句连接起来

$query->where([
    ['code', '>', 100],
    ['code', '<', 200],
]);

或通过链式方法达到相同的结果

$query->where('code', '>', 100)->andWhere('code', '<', 200);

OR-逻辑

$query->where('name', 'Ivan')->orWhere('id', 5);

方法where()andWhere()orWhere()使用相同的签名,因此您可以将它们组合使用

$query->where('code', '>', 100)
    ->andWhere('code', '<', 500)
    ->orWhere([
        ['code', '<', 100],
        ['id', '=', 5]
    ]);

正则表达式过滤

除了逻辑运算符之外,您还可以在where语句中使用关键字likematch

关键字match允许您将REGEXP_MATCH()函数应用于过滤公式。Airtable的正则表达式函数使用RE2正则表达式库实现,因此请确保您的正则表达式语法正确。

// look for emails, matching @gmail.com in case-insensitive way
$query->where('email', 'match', '(?i)^(.+)@gmail.com$');

关键字like在底层也使用REGEXP_MATCH(),但它提供了更类似SQL的语法。

// look for emails, which ends with @gmail.com
$query->where('email', 'like', '%@gmail.com');

// look for names, which starts with Ivan
$query->where('name', 'like', 'Ivan%');

// look for urls, which contains substring (both variants below works the same):
$query->where('url', 'like', '%github%');
$query->where('url', 'like', 'github');

请注意,like是区分大小写的,因此如果您想忽略大小写,最好使用带有i标志的match

日期过滤

库提供了一些方法来按日期和时间过滤记录

$query->whereDate('birthdate', new \DateTimeImmutable('2022-03-08'));
$query->whereDateTime('meeting_start', '2022-04-01 11:00:00');

第一个参数是您的列名。

您可以通过传递DateTimeImmutable对象或datetime字符串,该字符串将自动转换为DateTimeImmutable

您可以通过日期范围而不是严格相等来过滤记录

$query
    ->whereDate('birthdate', '>=', new \DateTimeImmutable('2022-03-01'))
    ->andWhereDate('birthdate', '<', new \DateTimeImmutable('2022-04-01')); 

为此提供了一些快捷方式

$query->whereDateBetween('birthdate', '2022-03-01', '2022-03-31'); // left and right borders included!
$query->whereDateTimeBetween('meeting_start', '2022-04-01 11:00:00', '2022-04-01 15:00:00');

当按日期搜索(而不是datetime)时,库在底层应用范围过滤器。例如,$query->whereDate('meeting', '2022-03-08')实际上会搜索从2022-03-08 00:00:002022-03-08 23:59:59的记录,包括左右边界。

请注意,该库不执行任何时区转换,因此最可靠的方法是在您的DateTimeImmutable对象中指定GMT时区,并在日期时间列设置中设置标志使用相同的时区(GMT)为所有协作者

原始公式

您可以看到构建的精确公式

$query->where([
    ['Code', '>', 100],
    ['Code', '<', 300]
])
->orWhere('Name', 'Qux');
    
$query->getFormula(); // OR(AND({Code}>'100', {Code}<'300'), {Name}='Qux')

您还可以通过原始公式过滤记录

$query->whereRaw("OR( AND({Code}>'100', {Code}<'300'), {Name}='Qux' )");

所有查询构建器方法都用于构建底层的原始公式。这意味着如果查询构建器的功能不足,您始终可以使用原始公式。

请注意,库不验证原始公式,因此您可能会从Airtable API收到异常。

视图

有时,创建具有预定义排序和过滤器的特定表视图比在源代码中构建复杂查询更方便。

假设您有一个包含只按优先级排序的活跃任务的 tasks 表和 active tasks 视图

$records = $client->table('tasks')
    ->select('*')
    ->whereView('active tasks')
    ->execute();

您可以将视图和额外的过滤器结合使用,指定所选字段的子集,并覆盖顺序,就像正常的选择查询一样

$records = $client->table('tasks')
    ->select('Name', 'Priority')
    ->whereView('active tasks')
    ->andWhere('Status', 'todo')
    ->orderBy(['Id' => 'desc'])
    ->execute();

您可以使用别名 andWhereView(),但方法 orWhereView() 会抛出 LogicError。这是因为视图实际上不是过滤器公式的组成部分,它始终像“视图 AND 公式”一样工作,所以您不能在这里使用 OR 操作符。

请注意,如果视图不存在,将抛出 RequestError 异常。

您可以使用宏扩展查询构建器方法

\Zadorin\Airtable\Client::macro('whereCanDriveCar', function() {
    $this->where('age', '>=', 21);
});

$query->where('state', 'Florida')->andWhereCanDriveCar();

宏名称不能以 or/and 开头。这些逻辑前缀是保留的,并自动处理。

宏回调中的上下文 $this 指的是查询构建器实例。这允许您使用其他查询构建器方法或甚至是其他宏

Client::macro('whereStateIsFlorida', function () {
    $this->where('state', 'Florida');
});

Client::macro('canDriveCar', function() {
    $this->where('age', '>=', 21);
});

Client::macro('whereFloridaDriver', function() {
    $this->whereStateIsFlorida()->andCanDriveCar();
});

您可以将变量传递到宏回调

Client::macro('whereName', function ($name) {
    $this->where('Name', '=', $name);
});

$query->whereName('Ivan')->orWhereName('John');

当然,您可以使用原始公式构建更复杂的东西

Client::macro('whereBornInMay', function($year) {
    $this->whereRaw("AND(IS_AFTER(birthdate, '$year-04-30 23:59:59'), IS_BEFORE(birthdate, '$year-06-01 00:00:00'))");
});

但请记住,原始公式会覆盖其他查询构建器设置。

类型转换

Airtable 支持链接字段,它引用当前表或另一表中的其他行。假设您有一个包含 contacts 字段的 users 表,该字段是到另一表中的行的链接。

默认情况下,您必须在插入或更新此类字段时指定具体的行 ID

$client
    ->table('users')
    ->insert(['name' => 'Ivan', 'contacts' => 'recSPVbdx5vXwyLoH'])
    ->execute();

这并不方便,因此 Airtable API 支持使用 typecast 参数自动将字符串值转换为数据。

默认情况下禁用自动转换以确保数据完整性,但有时这可能会很有用。

这就是您如何启用该功能的方式

$client
    ->table('users')
    ->insert(['name' => 'Ivan', 'contacts' => 'ivan@test.tld'])
    ->typecast(true) // true is default value and can be skipped
    ->execute();

更新查询的工作方式相同。

节流

Airtable API 每个基础的限制为每秒 5 个请求。客户端使用简单的节流库来保持此限制。

您可以禁用此行为

$client = new \Zadorin\Airtable\Client($apiKey, $database);
$client->throttling(false);

调试

客户端保留最后一个请求对象,因此您可以将其用于调试目的。

请注意调试信息,因为它包含所有 HTTP 头,包括授权令牌

$recordset = $client->table($tableName)->select('*')->execute();
$request = $client->getLastRequest();

$request->getResponseCode(); // http code (int)
$request->getPlainResponse(); // response body (string)
$request->getResponseInfo(); // array provided by curl_getinfo()

异常

所有包异常都继承自公共 Zadorin\Airtable\Errors\AirtableError 类。

您可能还感兴趣于 Zadorin\Airtable\Errors\RequestError,它包含最后一个请求实例

try {
    $inserted = $client->table($tableName)->insert()->execute();
} catch (RequestError $e) {
    
    // catch Airtable responses here
    var_dump($e->getMessage());
    var_dump($e->getLastRequest()->getResponseInfo());

} catch (AirtableError $e) {

    // catch package errors. In that case it will be "No records specified for insert"

}

已知问题

客户端使用 ext-curl 进行请求,使用 ext-json 编码/解码结果。请确保已安装并正确配置了这些 php 扩展。

如果您看到 SSL 证书问题:无法获取本地颁发者证书,您可能需要配置 php.ini 中的 curl.cainfo 选项。 来源

许可证和贡献

MIT 许可证。任何反馈都备受赞赏——欢迎到 问题

如果您想发送拉取请求,请确保所有测试都通过。

测试

将此 只读测试数据库 复制到您的 Airtable 账户中,然后填写 phpunit.xml.dist 中指定的环境变量。

最后运行测试套件

./vendor/bin/pest

还建议使用静态分析工具以避免错误

./vendor/bin/psalm