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. 05Appwrite 関数の実行

活用シーン

  1. 01PHP サーバーからの Appwrite ユーザー管理
  2. 02TablesDB でのデータ作成と検索
  3. 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 型は非推奨です。新しいカラムではすべて明示的なカラム型を使用してください。

型最大文字数インデックスストレージ
varchar16,383完全インデックス(サイズが 768 以下の場合)行内にインライン
text16,383プレフィックスのみオフページ
mediumtext4,194,303プレフィックスのみオフページ
longtext1,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()) — リソースが誰でも読み取り可能になります

コメント