> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 快取

> 學習如何運用 Laravel 的快取系統來提升應用程式的效能。

## 快取是什麼

資料庫查詢或外部 API 呼叫的 CPU 與網路成本較高，處理時間可能長達數秒。
若需重複取得相同資料，可以將結果儲存在**快取**中，就能加速後續請求的處理。

Laravel 提供統一的 API，可對應 Memcached、Redis、DynamoDB、資料庫等多種快取後端。

<Info>
  預設會使用 `database` 驅動。你可以改用 Redis 或 Memcached，以獲得更快的快取效能。
</Info>

## 快取設定

### config/cache.php

快取的設定集中在 `config/cache.php`。
可透過 `CACHE_STORE` 環境變數切換預設的驅動。

```php theme={null}
// config/cache.php
'default' => env('CACHE_STORE', 'database'),
```

### 可用的驅動

<AccordionGroup>
  <Accordion title="database（預設）">
    將序列化後的快取資料儲存到資料庫資料表。
    Laravel 11 以後的新專案已內建對應 migration。

    ```ini theme={null}
    CACHE_STORE=database
    ```

    如果尚未包含 migration，可以透過 Artisan 指令建立。

    ```shell theme={null}
    php artisan make:cache-table
    php artisan migrate
    ```
  </Accordion>

  <Accordion title="file">
    將快取資料儲存在檔案系統中。
    無需額外設定，適合小型應用程式。

    ```ini theme={null}
    CACHE_STORE=file
    ```
  </Accordion>

  <Accordion title="redis">
    以記憶體運作的高速快取驅動。
    是正式環境中最常使用的選擇。需要 PhpRedis PHP 擴充或 `predis/predis` 套件。

    ```ini theme={null}
    CACHE_STORE=redis
    REDIS_HOST=127.0.0.1
    REDIS_PORT=6379
    ```
  </Accordion>

  <Accordion title="memcached">
    需要 Memcached PECL 套件。
    請於 `config/cache.php` 設定伺服器。

    ```php theme={null}
    'memcached' => [
        'servers' => [
            [
                'host' => env('MEMCACHED_HOST', '127.0.0.1'),
                'port' => env('MEMCACHED_PORT', 11211),
                'weight' => 100,
            ],
        ],
    ],
    ```
  </Accordion>

  <Accordion title="dynamodb">
    使用 AWS DynamoDB 作為快取儲存。
    請先建立 DynamoDB 資料表，並安裝 AWS SDK。

    ```shell theme={null}
    composer require aws/aws-sdk-php
    ```

    ```ini theme={null}
    CACHE_STORE=dynamodb
    DYNAMODB_CACHE_TABLE=cache
    AWS_DEFAULT_REGION=us-east-1
    AWS_ACCESS_KEY_ID=your-key-id
    AWS_SECRET_ACCESS_KEY=your-secret-key
    ```
  </Accordion>

  <Accordion title="storage">
    使用任意的檔案系統磁碟作為 key/value 快取儲存。
    如果想直接使用既有的 S3 磁碟做快取，這相當方便。

    ```php theme={null}
    'storage' => [
        'driver' => 'storage',
        'disk' => env('CACHE_STORAGE_DISK'),
        'path' => env('CACHE_STORAGE_PATH', 'framework/cache/data'),
    ],
    ```
  </Accordion>

  <Accordion title="array / null（測試用）">
    `array` 是只在單次請求中有效的記憶體內快取。
    `null` 則會忽略所有操作。這兩者在自動化測試時都相當實用。

    ```ini theme={null}
    CACHE_STORE=array
    ```
  </Accordion>
</AccordionGroup>

### 快取驅動的階層結構

依用途與速度不同來選用不同驅動。

```mermaid theme={null}
flowchart LR
    A["應用程式"] --> B["Cache Facade"]

    subgraph L1 ["L1：請求內（最快）"]
        C["array<br>測試 / 開發用"]
        D["memo（包裝器）<br>減少重複存取"]
    end

    subgraph L2 ["L2：記憶體內（高速）"]
        E["Redis<br>正式環境推薦"]
        F["Memcached<br>分散式快取"]
    end

    subgraph L3 ["L3：持久化儲存（標準）"]
        G["database（預設）"]
        H["file"]
        I["DynamoDB（AWS）"]
        J["storage<br>S3 等磁碟"]
    end

    B --> C
    B --> D
    B --> E
    B --> F
    B --> G
    B --> H
    B --> I
    B --> J
```

## 基本操作

### 取得 Cache Facade

透過 `Cache` Facade 操作快取。

```php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
    public function index(): array
    {
        $value = Cache::get('key');

        return [
            // ...
        ];
    }
}
```

若要切換不同的快取儲存，可以使用 `store()` 方法。

```php theme={null}
$value = Cache::store('file')->get('foo');

Cache::store('redis')->put('bar', 'baz', 600); // 保存 10 分鐘
```

### 讀取資料：`Cache::get()`

透過 `get()` 方法從快取中讀取資料。
若快取不存在則回傳 `null`。也可以指定預設值。

```php theme={null}
$value = Cache::get('key');

// 指定預設值
$value = Cache::get('key', 'default');

// 透過閉包延遲取得預設值
$value = Cache::get('key', function () {
    return DB::table('settings')->get();
});
```

### 儲存資料：`Cache::put()`

透過 `put()` 方法將資料存入快取。第 3 個參數指定有效期限（秒）。

```php theme={null}
// 保存 10 秒
Cache::put('key', 'value', 10);

// 用 Carbon 實例指定有效期限
Cache::put('key', 'value', now()->plus(minutes: 10));

// 無有效期限（永久保存）
Cache::put('key', 'value');
```

也可以透過 `add()` 方法，只有在快取不存在時才儲存。

```php theme={null}
// 只有不存在時才新增（原子操作）
Cache::add('key', 'value', $seconds);
```

若要永久保存，可使用 `forever()`。

```php theme={null}
Cache::forever('key', 'value');
```

### 讀取或儲存：`Cache::remember()`

這是**最常使用的操作**。若快取中已有資料則回傳它，若沒有則執行閉包並儲存其結果。

```php theme={null}
$users = Cache::remember('users', 3600, function () {
    return DB::table('users')->get();
});
```

<Tip>
  使用 `Cache::remember()` 可以將「檢查快取 → 若無則取得 → 存入快取」的 3 個步驟，濃縮成 1 行程式碼。非常適合快取資料庫查詢結果或外部 API 回應。
</Tip>

### remember() 的流程

```mermaid theme={null}
flowchart TD
    A["Cache::remember('key', $ttl, fn)"] --> B{"快取儲存中<br>是否存在 'key'？"}
    B -->|"命中 (Hit)"| C["從快取讀取資料"]
    B -->|"未命中 (Miss)"| D["執行閉包 fn<br>例：DB 查詢 / API 呼叫"]
    D --> E["將結果存入快取<br>有效期限 = $ttl 秒"]
    E --> F["回傳資料"]
    C --> F
```

也有永久保存版本 `rememberForever()`。

```php theme={null}
$value = Cache::rememberForever('users', function () {
    return DB::table('users')->get();
});
```

若想知道是快取「命中」還是透過閉包新取得的，可以使用 `rememberWithWarmth()` 方法。此方法會以陣列回傳快取值以及一個布林值，表示該值是否為「熱資料」（來自快取）。

```php theme={null}
[$value, $warm] = Cache::rememberWithWarmth('users', 3600, function () {
    return DB::table('users')->get();
});

if ($warm) {
    // 來自快取的資料
} else {
    // 透過閉包新取得的資料
}
```

<Tip>
  `rememberWithWarmth()` 適合用來監控快取的效率。若 \$warm 常常是 `false`，可能需要調整快取有效期限（TTL）以改善。
</Tip>

#### Stale While Revalidate（彈性快取更新）

`Cache::flexible()` 以陣列指定快取的「新鮮期間」與「即使變舊仍可使用的期間」。
此模式會在把舊資料回傳給使用者的同時，於背景更新快取。

```php theme={null}
// [新鮮期間 (秒), 過期後仍可使用期間 (秒)]
$value = Cache::flexible('users', [5, 10], function () {
    return DB::table('users')->get();
});
```

### 存在確認：`Cache::has()`

```php theme={null}
if (Cache::has('key')) {
    // 快取存在時的處理
}
```

### 值的遞增 / 遞減

可以操作整數計數器。

```php theme={null}
// 若值不存在則初始化
Cache::add('key', 0, now()->addHours(4));

// 遞增 / 遞減
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);
```

### 讀取並刪除：`Cache::pull()`

讀取後即從快取中刪除。適合管理一次性資料。

```php theme={null}
$value = Cache::pull('key');
```

### 刪除資料：`Cache::forget()`

```php theme={null}
// 刪除特定的 key
Cache::forget('key');

// 清除全部快取
Cache::flush();
```

<Warning>
  `Cache::flush()` 會忽略快取「prefix」設定，刪除所有項目。若多個應用程式共用快取請務必留意。
</Warning>

可以透過 `Cache::flushLocks()` 清除快取中所有原子鎖。

```php theme={null}
Cache::flushLocks();
```

### TTL 延長：`Cache::touch()`

延長既有快取項目的有效期限。

```php theme={null}
// 以秒數指定
Cache::touch('key', 3600);

// 以 Carbon 實例指定
Cache::touch('key', now()->addHours(2));
```

## 快取記憶化（Memoization）

使用 `memo` 驅動可將單一請求內的快取存取暫存於記憶體中。
若同一個 key 常被重複存取，可以減少對快取儲存的往返次數。

```php theme={null}
use Illuminate\Support\Facades\Cache;

// 記憶化預設 store
$value = Cache::memo()->get('key');

// 記憶化 Redis store
$value = Cache::memo('redis')->get('key');
```

```php theme={null}
// 首次存取會觸及快取儲存
$value = Cache::memo()->get('key');

// 第二次以後直接從記憶體回傳（不觸及儲存）
$value = Cache::memo()->get('key');
```

## 快取標籤（Tags）

可以透過標籤將相關的快取項目分組，並一併刪除。

<Warning>
  快取標籤無法用於 `file`、`dynamodb`、`database`、`storage` 驅動。需要 `redis` 或 `memcached` 驅動。
</Warning>

### 帶標籤快取的結構

被貼上多個標籤的快取項目，可以透過任一標籤一起刪除。

```mermaid theme={null}
flowchart TD
    A["Cache::tags(['people', 'artists'])<br>.put('John', $data)"] --> B["John 的快取"]
    C["Cache::tags(['people', 'authors'])<br>.put('Anne', $data)"] --> D["Anne 的快取"]

    B --> T1["標籤：people"]
    B --> T2["標籤：artists"]
    D --> T1
    D --> T3["標籤：authors"]

    T1 -->|"flush()"| E["刪除 John 和 Anne"]
    T2 -->|"flush()"| F["只刪除 John"]
    T3 -->|"flush()"| G["只刪除 Anne"]
```

### 儲存與讀取帶標籤的快取

```php theme={null}
use Illuminate\Support\Facades\Cache;

// 帶標籤儲存
Cache::tags(['people', 'artists'])->put('John', $john, $seconds);
Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);

// 指定標籤讀取
$john = Cache::tags(['people', 'artists'])->get('John');
$anne = Cache::tags(['people', 'authors'])->get('Anne');
```

### 刪除帶標籤的快取

```php theme={null}
// 刪除擁有 'people' 與 'authors' 標籤的所有快取（John 和 Anne 皆刪除）
Cache::tags(['people', 'authors'])->flush();

// 只刪除 'authors' 標籤（只刪除 Anne，John 保留）
Cache::tags('authors')->flush();
```

<Tip>
  當你想以使用者、文章等單位為群組進行快取失效時，標籤非常實用。
  例如：`Cache::tags(['user', "user:{$userId}"])->flush()` 可以清除該使用者相關的所有快取。
</Tip>

## 原子操作（Lock）

透過 `Cache::lock()` 可以實作分散式鎖，防止多程序或並行請求造成的競態情況。

<Info>
  此功能可用於 `memcached`、`redis`、`dynamodb`、`database`、`file`、`array` 快取驅動。所有伺服器都必須連線至同一個中央快取伺服器。
</Info>

### 基本鎖操作

```php theme={null}
use Illuminate\Support\Facades\Cache;

$lock = Cache::lock('foo', 10);

if ($lock->get()) {
    // 成功取得鎖（10 秒內有效）

    // 執行某些互斥處理

    $lock->release();
}
```

若傳入閉包，處理完成後會自動釋放鎖。

```php theme={null}
Cache::lock('foo', 10)->get(function () {
    // 自動釋放鎖
});
```

### 鎖的等待

在取得鎖之前最多等待指定秒數。若超時則會拋出 `LockTimeoutException`。

```php theme={null}
use Illuminate\Contracts\Cache\LockTimeoutException;

$lock = Cache::lock('foo', 10);

try {
    $lock->block(5); // 最多等待 5 秒

    // 取得鎖之後的處理

} catch (LockTimeoutException $e) {
    // 取得鎖失敗
} finally {
    $lock->release();
}
```

使用閉包可以更精簡。

```php theme={null}
Cache::lock('foo', 10)->block(5, function () {
    // 最多等待 5 秒取得鎖，處理完自動釋放
});
```

### 防止重複執行：`withoutOverlapping()`

一種簡單防止同一處理重複執行的方法。

```php theme={null}
Cache::withoutOverlapping('foo', function () {
    // 同時間只會執行一次
});

// 自訂等待時間與鎖持有時間
Cache::withoutOverlapping('foo', function () {
    // ...
}, lockFor: 120, waitFor: 5);
```

### 限制並發數：`funnel()`

限制可同時執行的處理數。

```php theme={null}
Cache::funnel('foo')
    ->limit(3)          // 允許最多 3 個並發執行
    ->releaseAfter(60)  // 60 秒後自動釋放
    ->block(10)         // 最多等待 10 秒
    ->then(function () {
        // 取得並發名額時的處理
    }, function () {
        // 沒能取得名額時的處理
    });
```

### 跨程序傳遞鎖

例如在 Web 請求中取得鎖，並在佇列 job 中釋放它。

```php theme={null}
// 於請求中取得鎖並派發 job
$lock = Cache::lock('processing', 120);

if ($lock->get()) {
    ProcessPodcast::dispatch($podcast, $lock->owner());
}

// 在佇列 job 內釋放鎖
Cache::restoreLock('processing', $this->owner)->release();
```

若要不論目前持有者為誰皆強制釋放鎖，可以使用 `forceRelease` 方法。

```php theme={null}
Cache::lock('processing')->forceRelease();
```

### 鎖的重新整理

若要延長目前持有鎖的有效期限，可以使用 `refresh` 方法。若不指定秒數，會使用取得鎖時原本的有效期限。當長時間處理時想定期延長較短的鎖，就不必一開始就設定極長的有效期限。

```php theme={null}
$lock = Cache::lock('generate-reports', 60);

if ($lock->get()) {
    foreach ($reports as $report) {
        $report->generate();

        // 再延長鎖 60 秒
        $lock->refresh();
    }

    $lock->release();
}
```

## 快取 Helper

透過 `cache()` 輔助函式，可以更簡潔地執行與 `Cache` Facade 相同的操作。

```php theme={null}
// 取得值
$value = cache('key');

// 儲存值（含有效期限）
cache(['key' => 'value'], $seconds);
cache(['key' => 'value'], now()->addMinutes(10));

// 不傳參數則取得與 Facade 相同的實例
cache()->remember('users', $seconds, function () {
    return DB::table('users')->get();
});
```

## 實戰範例

### 快取資料庫查詢結果

<Steps>
  <Step title="在控制器中快取查詢結果">
    ```php theme={null}
    use Illuminate\Support\Facades\Cache;
    use Illuminate\Support\Facades\DB;

    public function index(): array
    {
        $users = Cache::remember('all-users', 3600, function () {
            return DB::table('users')->orderBy('name')->get();
        });

        return compact('users');
    }
    ```
  </Step>

  <Step title="資料更新時清除快取">
    ```php theme={null}
    public function store(Request $request): RedirectResponse
    {
        User::create($request->validated());

        // 刪除快取，下次存取時重新取得
        Cache::forget('all-users');

        return redirect()->route('users.index');
    }
    ```
  </Step>
</Steps>

### 快取 Eloquent 模型

```php theme={null}
use App\Models\Product;
use Illuminate\Support\Facades\Cache;

// 依分類的商品列表快取 1 小時
public function byCategory(int $categoryId): array
{
    $products = Cache::remember(
        "products:category:{$categoryId}",
        3600,
        fn () => Product::where('category_id', $categoryId)
            ->where('is_active', true)
            ->orderBy('name')
            ->get()
    );

    return compact('products');
}
```

### 快取 API 回應

```php theme={null}
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

public function getWeather(string $city): array
{
    return Cache::remember(
        "weather:{$city}",
        1800, // 快取 30 分鐘
        function () use ($city) {
            $response = Http::get('https://api.weather.example.com/current', [
                'city' => $city,
            ]);

            return $response->json();
        }
    );
}
```

### 用快取標籤管理群組

```php theme={null}
use Illuminate\Support\Facades\Cache;

class ArticleController extends Controller
{
    public function show(Article $article): array
    {
        $data = Cache::tags(['articles', "article:{$article->id}"])
            ->remember("article:{$article->id}:detail", 3600, function () use ($article) {
                return $article->load(['author', 'tags', 'comments']);
            });

        return compact('data');
    }

    public function update(Request $request, Article $article): RedirectResponse
    {
        $article->update($request->validated());

        // 清除此文章相關的所有快取
        Cache::tags(["article:{$article->id}"])->flush();

        return redirect()->route('articles.show', $article);
    }
}
```

## 總結

<AccordionGroup>
  <Accordion title="常用方法一覽">
    | 方法                                 | 說明         |
    | ---------------------------------- | ---------- |
    | `Cache::get('key')`                | 讀取快取       |
    | `Cache::put('key', $value, $ttl)`  | 儲存快取       |
    | `Cache::remember('key', $ttl, fn)` | 讀取或儲存（最重要） |
    | `Cache::forget('key')`             | 刪除快取       |
    | `Cache::has('key')`                | 確認快取是否存在   |
    | `Cache::flush()`                   | 清除所有快取     |
    | `Cache::forever('key', $value)`    | 永久保存       |
    | `Cache::pull('key')`               | 讀取後刪除      |
    | `Cache::increment('key')`          | 數值遞增       |
    | `Cache::tags([...])->flush()`      | 清除帶標籤的快取   |
  </Accordion>

  <Accordion title="如何選擇驅動">
    * **開發、小型專案**：`file` 或 `database`
    * **正式、高流量**：`redis`（結合 Laravel Horizon 也便於監控）
    * **AWS 環境**：`dynamodb` 或 `storage`（想沿用 S3 磁碟時）
    * **測試**：`array` 或 `null`
  </Accordion>

  <Accordion title="快取的最佳實踐">
    * 快取 key 命名應在整個應用程式中保持唯一（例：`users:1:profile`）
    * 資料更新後透過 `Cache::forget()` 或 `Cache::tags()->flush()` 將快取失效
    * 快取 TTL 不宜過短或過長，應依資料更新頻率調整
    * 使用 `Cache::remember()` 可以簡潔地寫出快取未命中時的處理
    * 正式環境建議使用 Redis，並考慮設定容錯機制
  </Accordion>
</AccordionGroup>


## Related topics

- [用 Laravel 構建 MCP 伺服器](/zh-TW/advanced/mcp-server.md)
- [Laravel AI SDK](/zh-TW/ai-sdk.md)
- [MongoDB](/zh-TW/mongodb.md)
- [Eloquent 存取器、修改器與型別轉換（Casts）](/zh-TW/eloquent-mutators.md)
- [Laravel Octane](/zh-TW/octane.md)
