mcpField
A
appwrite-php

appwrite-php

github.com/appwrite开发28851 次安装1 周前更新
代码仓库

关于 appwrite-php

appwrite-php 是供 PHP 应用集成 Appwrite 的 SDK,面向 PHP 服务端开发者。它支持用户管理、TablesDB 查询、文件与权限管理、团队及云函数,并提供 SSR 认证示例。

主要功能

  1. 01用户管理
  2. 02TablesDB 数据操作与查询
  3. 03文件存储与权限设置
  4. 04团队管理
  5. 05调用 Appwrite 云函数

适用场景

  1. 01在 PHP 服务端管理 Appwrite 用户
  2. 02在 TablesDB 中创建和查询数据
  3. 03通过 SSR 应用处理 Appwrite 会话认证

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/appwrite/skills --skill appwrite-php

安装后在 Claude Code 中输入/appwrite-php确认已加载。

详细介绍

Appwrite PHP SDK

安装

composer require appwrite/appwrite

设置客户端

use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Query;
use Appwrite\Services\Users;
use Appwrite\Services\TablesDB;
use Appwrite\Services\Storage;
use Appwrite\Services\Functions;
use Appwrite\InputFile;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject(getenv('APPWRITE_PROJECT_ID'))
    ->setKey(getenv('APPWRITE_API_KEY'));

代码示例

用户管理
$users = new Users($client);

// Create user
$user = $users->create(ID::unique(), '[email protected]', null, 'password123', 'User Name');

// List users
$list = $users->list([Query::limit(25)]);

// Get user
$fetched = $users->get('[USER_ID]');

// Delete user
$users->delete('[USER_ID]');
数据库操作

注意: 所有新代码都应使用 TablesDB,不要使用已弃用的 Databases 类。只有在现有代码库已依赖 Databases 或用户明确要求时,才使用 Databases。

提示: 所有 SDK 方法调用都应优先使用命名参数(PHP 8+,例如 databaseId: '...')。只有在现有代码库已使用位置参数或用户明确要求时,才使用位置参数。

$tablesDB = new TablesDB($client);

// Create database
$db = $tablesDB->create(ID::unique(), 'My Database');

// Create row
$doc = $tablesDB->createRow('[DATABASE_ID]', '[TABLE_ID]', ID::unique(), [
    'title' => 'Hello World'
]);

// Query rows
$results = $tablesDB->listRows('[DATABASE_ID]', '[TABLE_ID]', [
    Query::equal('title', ['Hello World']),
    Query::limit(10)
]);

// Get row
$row = $tablesDB->getRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]');

// Update row
$tablesDB->updateRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]', [
    'title' => 'Updated'
]);

// Delete row
$tablesDB->deleteRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]');
字符串列类型

注意: 旧版 string 类型已弃用。所有新列都应使用明确的列类型。

类型最大字符数索引存储
varchar16,383完整索引(如果大小 ≤ 768)行内
text16,383仅前缀页外
mediumtext4,194,303仅前缀页外
longtext1,073,741,823仅前缀页外
  • varchar 数据存储在行内,并计入 64 KB 的行大小限制。短小且需要索引的字段(如名称、slug 或标识符)应优先使用此类型。
  • text、mediumtext 和 longtext 数据存储在页外(行内仅保留一个 20 字节的指针),因此不会占用行大小限额。这些类型不需要设置 size。
// Create table with explicit string column types
$tablesDB->createTable('[DATABASE_ID]', ID::unique(), 'articles', [
    ['key' => 'title',    'type' => 'varchar',    'size' => 255, 'required' => true],
    ['key' => 'summary',  'type' => 'text',                      'required' => false],
    ['key' => 'body',     'type' => 'mediumtext',                'required' => false],
    ['key' => 'raw_data', 'type' => 'longtext',                  'required' => false],
]);
查询方法
// Filtering
Query::equal('field', ['value'])            // == (always pass array)
Query::notEqual('field', ['value'])         // !=
Query::lessThan('field', 100)              // <
Query::lessThanEqual('field', 100)         // <=
Query::greaterThan('field', 100)           // >
Query::greaterThanEqual('field', 100)      // >=
Query::between('field', 1, 100)            // 1 <= field <= 100
Query::isNull('field')                     // is null
Query::isNotNull('field')                  // is not null
Query::startsWith('field', 'prefix')       // starts with
Query::endsWith('field', 'suffix')         // ends with
Query::contains('field', ['sub'])          // contains (string or array)
Query::search('field', 'keywords')         // full-text search (requires index)

// Sorting
Query::orderAsc('field')
Query::orderDesc('field')

// Pagination
Query::limit(25)                           // max rows (default 25, max 100)
Query::offset(0)                           // skip N rows
Query::cursorAfter('[ROW_ID]')             // cursor pagination (preferred)
Query::cursorBefore('[ROW_ID]')

// Selection & Logic
Query::select(['field1', 'field2'])        // return only specified fields
Query::or([Query::equal('a', [1]), Query::equal('b', [2])])   // OR
Query::and([Query::greaterThan('age', 18), Query::lessThan('age', 65)])  // AND (default)
Query::relationship('comments', [Query::equal('approved', [true]), Query::limit(25)])  // related documents

// Aggregations (optional alias)
Query::count('*', 'total')
Query::sum('amount', 'revenue')
Query::avg('score', 'avgScore')
Query::min('price', 'minPrice')
Query::max('price', 'maxPrice')

// Grouping
Query::groupBy(['status', 'year'])
Query::having([Query::greaterThan('total', 1)])
Query::distinct()

// Joins (table id, left key, right key, operator, alias)
Query::join('orders', '$id', 'customerId')
Query::leftJoin('orders', '$id', 'customerId', '=', 'ord')

// Nested join ON: extra predicates stay on the JOIN, not WHERE
Query::leftJoin('orders', 'ord', [
    Query::on('$id', 'customerId'),
    Query::equal('ord.status', 'paid'),
])

// Fluent builder. Pass build() into list methods.
$queries = Query::builder()
    ->equal('status', 'published')
    ->leftJoin('orders', '$id', 'customerId', '=', 'ord')
    ->count('*', 'total')
    ->groupBy(['status'])
    ->limit(25)
    ->build();

$tablesDB->listRows('[DATABASE_ID]', '[TABLE_ID]', $queries);
文件存储
$storage = new Storage($client);

// Upload file
$file = $storage->createFile('[BUCKET_ID]', ID::unique(), InputFile::withPath('/path/to/file.png'));

// List files
$files = $storage->listFiles('[BUCKET_ID]');

// Delete file
$storage->deleteFile('[BUCKET_ID]', '[FILE_ID]');
InputFile 工厂方法
use Appwrite\InputFile;

InputFile::withPath('/path/to/file.png')                    // from filesystem path
InputFile::withData('Hello world', 'hello.txt')             // from string content
团队
$teams = new Teams($client);

// Create team
$team = $teams->create(ID::unique(), 'Engineering');

// List teams
$list = $teams->list();

// Create membership (invite user by email)
$membership = $teams->createMembership('[TEAM_ID]', ['editor'], email: '[email protected]');

// List memberships
$members = $teams->listMemberships('[TEAM_ID]');

// Update membership roles
$teams->updateMembership('[TEAM_ID]', '[MEMBERSHIP_ID]', ['admin']);

// Delete team
$teams->delete('[TEAM_ID]');

基于角色的访问控制: 设置权限时,对所有团队成员使用 Role::team('[TEAM_ID]');对特定团队角色使用 Role::team('[TEAM_ID]', 'editor')。

无服务器函数
$functions = new Functions($client);

// Execute function
$execution = $functions->createExecution('[FUNCTION_ID]', '{"key": "value"}');

// List executions
$executions = $functions->listExecutions('[FUNCTION_ID]');
编写函数处理程序(PHP 运行时)
// src/main.php — Appwrite Function entry point
return function ($context) {
    // $context->req->body        — raw body (string)
    // $context->req->bodyJson    — parsed JSON (array or null)
    // $context->req->headers     — headers (array)
    // $context->req->method      — HTTP method
    // $context->req->path        — URL path
    // $context->req->query       — query params (array)

    $context->log('Processing: ' . $context->req->method . ' ' . $context->req->path);

    if ($context->req->method === 'GET') {
        return $context->res->json(['message' => 'Hello from Appwrite Function!']);
    }

    $data = $context->req->bodyJson ?? [];
    if (!isset($data['name'])) {
        $context->error('Missing name field');
        return $context->res->json(['error' => 'Name is required'], 400);
    }

    return $context->res->json(['success' => true]);      // JSON
    // return $context->res->text('Hello');                // plain text
    // return $context->res->empty();                      // 204
    // return $context->res->redirect('https://...');      // 302
};
服务端渲染(SSR)身份验证

SSR 应用(Laravel、Symfony 等)使用服务端 SDK处理身份验证。你需要两个客户端:

  • 管理员客户端 — 使用 API 密钥创建会话并绕过速率限制(可复用的单例)
  • 会话客户端 — 使用会话 Cookie 代表用户执行操作(每个请求单独创建,绝不共享)
use Appwrite\Client;
use Appwrite\Services\Account;

// Admin client (reusable)
$adminClient = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('[PROJECT_ID]')
    ->setKey(getenv('APPWRITE_API_KEY'));

// Session client (create per-request)
$sessionClient = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('[PROJECT_ID]');

$session = $_COOKIE['a_session_[PROJECT_ID]'] ?? null;
if ($session) {
    $sessionClient->setSession($session);
}
邮箱/密码登录
$account = new Account($adminClient);
$session = $account->createEmailPasswordSession($email, $password);

// Cookie name must be a_session_<PROJECT_ID>
setcookie('a_session_[PROJECT_ID]', $session['secret'], [
    'httpOnly' => true,
    'secure' => true,
    'sameSite' => 'strict',
    'expires' => strtotime($session['expire']),
    'path' => '/',
]);
已验证身份的请求
$session = $_COOKIE['a_session_[PROJECT_ID]'] ?? null;
if (!$session) {
    http_response_code(401);
    exit;
}

$sessionClient->setSession($session);
$account = new Account($sessionClient);
$user = $account->get();
OAuth2 SSR 流程
// Step 1: Redirect to OAuth provider
$account = new Account($adminClient);
$redirectUrl = $account->createOAuth2Token(
    OAuthProvider::GITHUB(),
    'https://example.com/oauth/success',
    'https://example.com/oauth/failure',
);
header('Location: ' . $redirectUrl);

// Step 2: Handle callback — exchange token for session
$account = new Account($adminClient);
$session = $account->createSession($_GET['userId'], $_GET['secret']);

setcookie('a_session_[PROJECT_ID]', $session['secret'], [
    'httpOnly' => true, 'secure' => true, 'sameSite' => 'strict',
    'expires' => strtotime($session['expire']), 'path' => '/',
]);

Cookie 安全: 务必使用 httpOnly、secure 和 sameSite: 'strict' 来防止 XSS。Cookie 名称必须为 a_session_<PROJECT_ID>。

转发用户代理: 调用 $sessionClient->setForwardedUserAgent($_SERVER['HTTP_USER_AGENT']),以记录最终用户的浏览器信息,便于调试和保障安全。

错误处理

use Appwrite\AppwriteException;

try {
    $row = $tablesDB->getRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]');
} catch (AppwriteException $e) {
    echo $e->getMessage();   // human-readable error message
    echo $e->getCode();      // HTTP status code (int)
    echo $e->getType();      // Appwrite error type string (e.g. 'document_not_found')
    echo $e->getResponse();  // full response body (array)
}

常见错误代码:

代码含义
401未授权 — 缺少或无效的会话/API 密钥
403禁止访问 — 执行此操作的权限不足
404未找到 — 资源不存在
409冲突 — ID 重复或违反唯一性约束
429请求过多 — 请求次数过多,请等待一段时间后重试

权限与角色(重要)

Appwrite 使用权限字符串来控制对资源的访问。每项权限都将一个操作(read、update、delete、create,或授予 create + update + delete 权限的 write)与一个角色目标配对。默认情况下,没有用户拥有访问权限,除非在行/文件级别显式设置权限,或从表/存储桶设置中继承权限。权限是由 Permission 和 Role 辅助类构建的字符串数组。

use Appwrite\Permission;
use Appwrite\Role;
带权限的数据库行
$doc = $tablesDB->createRow('[DATABASE_ID]', '[TABLE_ID]', ID::unique(), [
    'title' => 'Hello World'
], [
    Permission::read(Role::user('[USER_ID]')),     // specific user can read
    Permission::update(Role::user('[USER_ID]')),   // specific user can update
    Permission::read(Role::team('[TEAM_ID]')),     // all team members can read
    Permission::read(Role::any()),                 // anyone (including guests) can read
]);
带权限的文件上传
$file = $storage->createFile('[BUCKET_ID]', ID::unique(), InputFile::withPath('/path/to/file.png'), [
    Permission::read(Role::any()),
    Permission::update(Role::user('[USER_ID]')),
    Permission::delete(Role::user('[USER_ID]')),
]);

何时设置权限: 当需要对每个资源进行访问控制时,设置行级/文件级权限。如果表中的所有行使用相同的规则,请在表/存储桶级别配置权限,并将行权限留空。

常见错误:

  • 忘记设置权限 — 资源将对所有用户(包括创建者)不可访问
  • 对 write/update/delete 使用 Role::any() — 允许任何用户(包括未经身份验证的访客)修改或移除资源
  • 对敏感数据使用 Permission::read(Role::any()) — 使资源可被公开读取

评论

支持 Markdown