appwrite-php について
appwrite-php は、PHP アプリに Appwrite を統合するための SDK で、PHP サーバーの開発者向けです。ユーザー管理、TablesDB のクエリ、ファイルと権限の管理、チーム、関数実行に対応し、SSR 認証の例も含みます。
主な機能
- 01ユーザー管理
- 02TablesDB の操作とクエリ
- 03ファイルストレージと権限設定
- 04チーム管理
- 05Appwrite 関数の実行
活用シーン
- 01PHP サーバーからの Appwrite ユーザー管理
- 02TablesDB でのデータ作成と検索
- 03SSR アプリでの 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]');
データベース操作
Note: 新しいコードではすべて
TablesDB(非推奨のDatabasesクラスではありません)を使用してください。Databasesは、既存のコードベースがすでに依存している場合、またはユーザーが明示的に要求した場合にのみ使用してください。Tip: 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]');
文字列カラム型
Note: レガシーの
string型は非推奨です。新しいカラムではすべて明示的なカラム型を使用してください。
| 型 | 最大文字数 | インデックス | ストレージ |
|---|---|---|---|
varchar | 16,383 | 完全インデックス(サイズが 768 以下の場合) | 行内にインライン |
text | 16,383 | プレフィックスのみ | オフページ |
mediumtext | 4,194,303 | プレフィックスのみ | オフページ |
longtext | 1,073,741,823 | プレフィックスのみ | オフページ |
varcharはインラインで保存され、64 KB の行サイズ制限にカウントされます。名前、スラッグ、識別子など、短くインデックスされたフィールドに適しています。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 を使用します。2 つのクライアントが必要です。
- 管理者クライアント — 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 のセキュリティ: XSS を防ぐために、常に
httpOnly、secure、sameSite: 'strict'を使用してください。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())— リソースが誰でも読み取り可能になります