定制 ntanduy/cloudflare-d1-database 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

ntanduy/cloudflare-d1-database

最新稳定版本:v0.8.0

Composer 安装命令:

composer require ntanduy/cloudflare-d1-database

包简介

Cloudflare D1 database driver for Laravel — full Eloquent & Query Builder support.

README 文档

README

codecov Tests PHP Laravel Latest Stable Version Total Downloads Monthly Downloads License

Use Cloudflare D1 as a native Laravel database driver — full Eloquent ORM, Query Builder, and Migration support.

🎯 Requirements

  • PHP: >= 8.2
  • Laravel: 10.x, 11.x, 12.x, or 13.x

✨ Features

  • Full Laravel Integration — Eloquent ORM, Query Builder, Migrations, Seeding
  • Two Connection Drivers — REST API (zero infrastructure) or Worker (low latency)
  • Batch Queries — Execute multiple statements in a single HTTP round-trip
  • Bulk Insert — Insert hundreds of rows in a single atomic batch call
  • Sessions / Read Replication — Leverage D1 global read replicas for lower-latency reads (Worker driver)
  • Auto Read/Write Splitting — Automatic routing of SELECTs to replicas and writes to primary (Worker driver)
  • Schema Dump — Export your D1 database via php artisan d1:schema-dump
  • Database Info — Inspect your D1 database with php artisan d1:info
  • Circuit Breaker — Fail fast on sustained outages instead of blocking on retries
  • Automatic Retries — Exponential backoff with jitter for 5xx/429 errors
  • Query Logging — Optional callback for monitoring and debugging
  • Health Check — Built-in php artisan d1:health to verify connection and measure latency

🚀 Installation

composer require ntanduy/cloudflare-d1-database

👏 Usage

Step 1: Publish Configuration

php artisan vendor:publish --tag="d1-config"

This creates config/d1-database.php with all available options.

Step 2: Choose a Driver

This package supports two drivers to connect Laravel with Cloudflare D1:

Driver How it works Latency Setup
REST (default) Calls Cloudflare D1 REST API directly ~100-500ms/query API Token only
Worker Routes queries through your own Cloudflare Worker ~10-50ms/query Requires deploying a Worker

Driver 1: REST API (Default)

The simplest setup — no extra infrastructure needed. Queries are sent to Cloudflare's REST API.

Add to your .env:

CF_D1_API_TOKEN=your_api_token
CF_D1_ACCOUNT_ID=your_account_id
CF_D1_DATABASE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

How to get these values:

  1. API Token — Go to Cloudflare Dashboard → API Tokens → Create Token → use the "Edit Cloudflare D1" template
  2. Account ID — Found on your Cloudflare Dashboard overview page (right sidebar)
  3. Database ID — Go to Workers & Pages → D1 → click your database → copy the Database ID

That's it! Your Laravel app can now use D1.

Driver 2: Worker (Low Latency)

For production apps that need lower latency, deploy a Cloudflare Worker as a proxy between Laravel and D1.

Add to your .env:

CF_D1_DRIVER=worker
CF_D1_WORKER_URL=https://your-d1-worker.your-subdomain.workers.dev
CF_D1_WORKER_SECRET=a-strong-shared-secret

Deploy the Worker

A ready-to-deploy Worker template is included in the Worker/ directory. To deploy:

cd Worker
npm install
npx wrangler secret put WORKER_SECRET
npm run deploy

Before deploying, update wrangler.jsonc with your D1 database binding:

name = "ntanduy-d1-worker"
main = "src/index.ts"
compatibility_date = "2026-03-10"

[[d1_databases]]
binding = "DB"
database_name = "your-database-name"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Important: Set WORKER_SECRET using npx wrangler secret put WORKER_SECRET — never put secrets in wrangler.jsonc. This secret must match the CF_D1_WORKER_SECRET in your Laravel .env.

Worker Endpoints

The Worker exposes these endpoints:

Endpoint Method Auth Description
/health GET Health check
/query POST ✅ Bearer Execute a single SQL query
/batch POST ✅ Bearer Execute multiple statements atomically
/exec POST ✅ Bearer Execute raw DDL/migration SQL
/raw POST ✅ Bearer Execute a query and return raw array-of-arrays

Step 3: Set as Default Connection

To use D1 as the default database, add to your .env:

DB_CONNECTION=d1

Step 4: Verify Connection

Run the built-in health check to verify your setup:

php artisan d1:health
  D1 Health Check
  Connection : d1
  Driver     : worker

+-------------------------+---------+------------------------------------------+
| Check                   | Status  | Detail                                   |
+-------------------------+---------+------------------------------------------+
| worker_url configured   | ✓ OK    | https://d1-proxy.name.workers.dev        |
| worker_secret configured| ✓ OK    | ******cret                               |
| Query test passed       | ✓ OK    | SELECT 1 as ok                           |
| End-to-end latency      | ✓ OK    | 24 ms                                    |
+-------------------------+---------+------------------------------------------+

  Overall: HEALTHY ✓

Step 5: Run Migrations

php artisan migrate --database=d1

📖 Examples

Eloquent ORM

use App\Models\Post;

// Create
$post = Post::create([
    'title' => 'Hello from D1',
    'body' => 'This is stored in Cloudflare D1!',
]);

// Read
$posts = Post::where('published', true)->orderBy('created_at', 'desc')->get();

// Update
$post->update(['title' => 'Updated Title']);

// Delete
$post->delete();

Query Builder

use Illuminate\Support\Facades\DB;

// Select
$users = DB::connection('d1')->table('users')
    ->where('active', true)
    ->limit(10)
    ->get();

// Insert
DB::connection('d1')->table('users')->insert([
    'name' => 'John Doe',
    'email' => 'john@example.com',
]);

// Raw queries
$results = DB::connection('d1')->select('SELECT * FROM users WHERE id = ?', [1]);

Query Logger

Monitor queries for debugging or performance analysis:

use Ntanduy\CFD1\D1\D1Connection;

/** @var D1Connection $connection */
$connection = DB::connection('d1');

$connection->d1()->setQueryLogger(function (
    string $query,
    array $params,
    float $timeMs,
    bool $success,
    ?array $error
) {
    if (! $success) {
        Log::error("D1 query failed: {$query}", [
            'params' => $params,
            'error' => $error,
            'time_ms' => $timeMs,
        ]);
    }
});

Runtime Driver Detection

use Ntanduy\CFD1\D1\D1Connection;

/** @var D1Connection $connection */
$connection = DB::connection('d1');

$connection->getDriver();      // 'rest' or 'worker'
$connection->isWorkerDriver(); // true or false

Batch Queries

Execute multiple SQL statements in a single HTTP round-trip. On the Worker driver, this uses D1's native batch() for atomic execution.

use Ntanduy\CFD1\D1\D1Connection;

/** @var D1Connection $connection */
$connection = DB::connection('d1');

$results = $connection->batch([
    ['sql' => 'SELECT * FROM users WHERE id = ?', 'params' => [1]],
    ['sql' => 'UPDATE stats SET views = views + 1 WHERE id = ?', 'params' => [5]],
    ['sql' => 'SELECT COUNT(*) as total FROM posts'],
]);

$user  = $results[0]; // Result set from first statement
$stats = $results[1]; // Result set from second statement
$count = $results[2]; // Result set from third statement

If any statement fails, a D1BatchException is thrown with the index of the failing statement:

use Ntanduy\CFD1\D1\Exceptions\D1BatchException;

try {
    $results = $connection->batch($statements);
} catch (D1BatchException $e) {
    // $e->getMessage() includes the failing statement index
}

Driver Feature Matrix

Feature REST Worker
Bulk Insert
Sessions / Read Replication ❌ Not supported ✅ Full support
Auto Read/Write Splitting ❌ Not supported ✅ Full support
Schema Dump ✅ (via REST credentials)
Database Info (d1:info) ✅ Full metadata ✅ Query test + REST metadata
Batch Queries
Circuit Breaker
Automatic Retries

Bulk Insert

Insert multiple rows efficiently using D1 batch execution — one HTTP round-trip, atomic:

use Ntanduy\CFD1\D1\D1Connection;

/** @var D1Connection $connection */
$connection = DB::connection('d1');

$connection->bulkInsert('users', [
    ['name' => 'Alice', 'email' => 'alice@example.com'],
    ['name' => 'Bob', 'email' => 'bob@example.com'],
    ['name' => 'Charlie', 'email' => 'charlie@example.com'],
]);
  • Each row becomes a parameterized INSERT (SQL injection safe)
  • All rows are sent as a D1 batch (atomic — if any fails, none are applied)
  • Rows exceeding D1's 100-statement batch limit are automatically chunked
  • Works with both REST and Worker drivers

Sessions / Read Replication (Worker Driver Only)

D1 supports global read replication — read queries can be served by nearby replicas for lower latency. The Sessions API ensures sequential consistency across queries.

Important: Sessions are only available with the Worker driver. The REST API does not support D1 Sessions — this is a Cloudflare platform limitation.

Enable via Config

Add to your .env:

CF_D1_SESSION_ENABLED=true
CF_D1_SESSION_MODE=first-unconstrained   # or 'first-primary'

This automatically enables sessions for all queries on the Worker driver.

Enable Programmatically

use Ntanduy\CFD1\D1\D1Connection;

/** @var D1Connection $connection */
$connection = DB::connection('d1');

// Start a session — first query goes to any instance (fastest)
$connection->withSession('first-unconstrained');

// Or start with the latest data from primary
$connection->withSession('first-primary');

// Execute queries — bookmarks are tracked automatically
$users = DB::table('users')->get();
$posts = DB::table('posts')->get();

// Get the current bookmark (for passing to another request/session)
$bookmark = $connection->getBookmark();

// Start a new session from a previous bookmark
$connection->withSession($bookmark);

// End the session when done
$connection->endSession();

Session Modes

Mode First Query Use When
first-unconstrained Any instance (primary or replica) Lowest latency, eventual consistency OK
first-primary Primary database Need the latest data for first query
<bookmark> At least as fresh as the bookmark Continuing from a previous session

How It Works

  1. PHP sends a session parameter with each query to the Worker
  2. Worker calls env.DB.withSession(param) to create a D1 session
  3. Worker returns a bookmark in the response
  4. PHP stores the bookmark and uses it for the next query
  5. This ensures sequential consistency across HTTP calls

Worker Template

The Worker template in Worker/ already includes session support. If you're upgrading from a previous version, redeploy the Worker:

cd Worker && npm run deploy

Auto Read/Write Splitting (Worker Driver Only)

Automatically route SELECT queries to D1 replicas and INSERT/UPDATE/DELETE to the primary — zero code changes required.

// config/database.php
'd1' => [
    'driver' => 'd1',
    'd1_driver' => 'worker',
    'worker_url' => env('CF_D1_WORKER_URL'),
    'worker_secret' => env('CF_D1_WORKER_SECRET'),

    'read' => [
        'session' => ['mode' => 'first-unconstrained'],
    ],
    'write' => [
        'session' => ['mode' => 'first-primary'],
    ],
    'sticky' => true,  // After write, reads use primary for consistency
],

Once configured, Laravel handles everything:

// Automatically goes to replica (fast, nearby)
$users = User::all();

// Automatically goes to primary
User::create(['name' => 'Alice', 'email' => 'alice@example.com']);

// With sticky=true, this read goes to primary (sees the new user)
$user = User::where('email', 'alice@example.com')->first();
  • sticky (default: true) — after a write, subsequent reads in the same request use the write connector's bookmark for sequential consistency
  • Works alongside manual withSession() — R/W splitting handles the base routing, you can still use sessions for fine-grained control
  • REST driver ignores read/write config — no sessions support, all queries go to primary

Database Info

Inspect your D1 database metadata and connection status:

php artisan d1:info

Displays database name, UUID, size, table count, read replication mode, R/W splitting status, circuit breaker state, and runs a query test.

# Specify a connection
php artisan d1:info --connection=d1

Uses the D1 REST API for metadata. Worker-only users see table count and query test but need REST credentials for full metadata.

Schema Dump

Export your D1 database schema (and optionally data) as a SQL file:

php artisan d1:schema-dump

This uses the D1 export REST API with polling mode. The dump is saved to database/schema/{connection}-schema.sql.

Options

# Schema only (no data)
php artisan d1:schema-dump --no-data

# Custom output path
php artisan d1:schema-dump --path=./backup.sql

# Delete migration files after dumping (same as native schema:dump --prune)
php artisan d1:schema-dump --prune

# Specify connection name
php artisan d1:schema-dump --connection=d1

Note: d1:schema-dump always uses the REST API for export, even when the Worker driver is your primary connection. Worker-only users must also set CF_D1_API_TOKEN, CF_D1_ACCOUNT_ID, and CF_D1_DATABASE_ID in their .env for the dump command to work.

Circuit Breaker

Prevents cascading failures when Cloudflare Workers experience cold starts or sustained outages. Instead of blocking for 30s+ on retries, the circuit breaker fails fast after consecutive failures.

States:

CLOSED → requests pass through normally
  ↓ (threshold consecutive failures)
OPEN → requests rejected immediately, no HTTP call
  ↓ (after cooldown seconds)
HALF_OPEN → one probe request allowed through
  ↓ success → CLOSED  |  failure → OPEN

Enable in your config (config/database.php or config/d1-database.php):

'd1' => [
    // ... other options ...
    'circuit_breaker' => [
        'enabled'      => env('CF_D1_CB_ENABLED', false),
        'threshold'    => env('CF_D1_CB_THRESHOLD', 5),     // failures before opening
        'cooldown'     => env('CF_D1_CB_COOLDOWN', 30),     // seconds before probe
        'cache_driver' => env('CF_D1_CB_CACHE_DRIVER', 'file'),
    ],
],

Handle the exception:

use Ntanduy\CFD1\D1\Exceptions\CircuitBreakerOpenException;

try {
    $users = DB::connection('d1')->table('users')->get();
} catch (CircuitBreakerOpenException $e) {
    // Circuit is open — fail fast, use fallback or return cached data
}

Note: Use file or redis as the cache_driver. Avoid database to prevent a dependency loop when D1 itself is down.

Retry & Backoff

The driver automatically retries failed requests with exponential backoff and jitter:

  • Retried: 5xx server errors, 429 rate limiting, connection timeouts
  • Not retried: 4xx client errors (400, 401, 403, 404, etc.)
CF_D1_RETRIES=2          # Max retry attempts (default: 2)
CF_D1_RETRY_DELAY=100    # Base delay in ms (default: 100)
CF_D1_TIMEOUT=10         # Request timeout in seconds (default: 10)
CF_D1_CONNECT_TIMEOUT=5  # Connection timeout in seconds (default: 5)

Backoff formula: delay × 2^(attempt-1) + random jitter (0-100ms)

Attempt Base Delay (100ms)
1 ~100-200ms
2 ~200-300ms

⚙️ Configuration Reference

Manual Setup (Alternative)

Instead of publishing the config, you can add the connection directly to config/database.php:

'connections' => [
    'd1' => [
        'driver' => 'd1',
        'd1_driver' => env('CF_D1_DRIVER', 'rest'),         // 'rest' or 'worker'
        'prefix' => '',
        'database' => env('CF_D1_DATABASE_ID', ''),

        // REST driver credentials
        'api' => 'https://api.cloudflare.com/client/v4',
        'auth' => [
            'token' => env('CF_D1_API_TOKEN', ''),
            'account_id' => env('CF_D1_ACCOUNT_ID', ''),
        ],

        // Worker driver credentials
        'worker_url' => env('CF_D1_WORKER_URL', ''),
        'worker_secret' => env('CF_D1_WORKER_SECRET', ''),

        // Performance tuning
        'timeout' => env('CF_D1_TIMEOUT', 10),
        'connect_timeout' => env('CF_D1_CONNECT_TIMEOUT', 5),
        'retries' => env('CF_D1_RETRIES', 2),
        'retry_delay' => env('CF_D1_RETRY_DELAY', 100),

        // Sessions / Read Replication (Worker driver only)
        'session' => [
            'enabled' => env('CF_D1_SESSION_ENABLED', false),
            'mode'    => env('CF_D1_SESSION_MODE', 'first-unconstrained'),
        ],

        // Circuit breaker (optional)
        'circuit_breaker' => [
            'enabled'      => env('CF_D1_CB_ENABLED', false),
            'threshold'    => env('CF_D1_CB_THRESHOLD', 5),
            'cooldown'     => env('CF_D1_CB_COOLDOWN', 30),
            'cache_driver' => env('CF_D1_CB_CACHE_DRIVER', 'file'),
        ],
    ],
],

Options Reference

Option Default Description
d1_driver rest Connection driver: rest (Cloudflare REST API) or worker (custom Worker)
database Your Cloudflare D1 Database ID
api https://api.cloudflare.com/client/v4 Cloudflare API base URL (REST driver only)
auth.token Cloudflare API Token (REST driver only)
auth.account_id Cloudflare Account ID (REST driver only)
worker_url Your Worker URL (Worker driver only)
worker_secret Shared secret for Worker auth (Worker driver only)
timeout 10 HTTP request timeout in seconds
connect_timeout 5 HTTP connection timeout in seconds
retries 2 Max retry attempts on 5xx/429 errors
retry_delay 100 Base delay between retries in milliseconds
session.enabled false Enable D1 sessions for read replication (Worker driver only)
session.mode first-unconstrained Session mode: first-primary or first-unconstrained
circuit_breaker.enabled false Enable circuit breaker for fail-fast behavior
circuit_breaker.threshold 5 Consecutive failures before opening the circuit
circuit_breaker.cooldown 30 Seconds before allowing a probe request
circuit_breaker.cache_driver file Laravel cache driver for circuit state (file, redis)

Environment Variables

# Driver selection
CF_D1_DRIVER=rest                    # 'rest' or 'worker'

# REST driver
CF_D1_API_TOKEN=your_api_token
CF_D1_ACCOUNT_ID=your_account_id
CF_D1_DATABASE_ID=your_database_id

# Worker driver
CF_D1_WORKER_URL=https://your-worker.workers.dev
CF_D1_WORKER_SECRET=your_shared_secret

# Performance tuning (optional)
CF_D1_TIMEOUT=10
CF_D1_CONNECT_TIMEOUT=5
CF_D1_RETRIES=2
CF_D1_RETRY_DELAY=100

# Sessions / Read Replication (Worker driver only)
CF_D1_SESSION_ENABLED=false
CF_D1_SESSION_MODE=first-unconstrained

# Circuit breaker (optional)
CF_D1_CB_ENABLED=false
CF_D1_CB_THRESHOLD=5
CF_D1_CB_COOLDOWN=30
CF_D1_CB_CACHE_DRIVER=file

⚠️ Limitations

  • No real transactions — D1 is stateless over HTTP and doesn't support BEGIN/COMMIT/ROLLBACK. The driver makes these methods no-ops so Laravel internals (auth, sessions, middleware) work without crashing.

    • DB::transaction(Closure) will execute the closure, but provides no atomicity — each query runs immediately and cannot be rolled back on failure.

    • DB::transaction(Closure, attempts: N) retries the closure on any exception, but without real deadlock detection or isolation.

    • For atomic multi-statement execution, use batch() which leverages D1's native batch API:

      $connection->batch([
          ['sql' => 'INSERT INTO orders ...', 'params' => [...]],
          ['sql' => 'UPDATE inventory ...', 'params' => [...]],
      ]);
  • REST API latency — Each query is an HTTP request (~100-500ms). Use the Worker driver for lower latency (~10-50ms).

  • Sessions / Read Replication — Worker driver only — The D1 Sessions API is only available via the Worker Binding. The REST API does not support sessions; all queries go to the primary database. This is a Cloudflare platform limitation.

  • Schema dump requires REST credentialsd1:schema-dump uses the D1 export REST API. Even Worker-only users must set CF_D1_API_TOKEN, CF_D1_ACCOUNT_ID, and CF_D1_DATABASE_ID.

  • Export blocks queries — During export, D1 may be unavailable for queries (Cloudflare limitation for large databases).

  • Bulk insert batch limit — D1 batch supports max 100 statements. bulkInsert() automatically chunks larger datasets but each chunk is a separate HTTP call.

  • No streaming — Large result sets are loaded entirely into memory.

🌱 Testing

PHP Tests

vendor/bin/pest

Worker Tests (Vitest)

cd Worker
npm ci
npm test

Local Development with Worker

Start the built-in Worker to test against a local D1 instance:

cd Worker
npm ci
npm run start

🤝 Contributing

Please see CONTRIBUTING for details.

🔒 Security

If you discover any security related issues, please email contact@ntanduy.com instead of using the issue tracker.

🎉 Credits

统计信息

  • 总下载量: 6.1k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 24
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 24
  • Watchers: 1
  • Forks: 7
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2024-08-22

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固