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

# 套件的 Storage 驅動程式註冊與重新解析

> 解讀 Laravel 13 的 FilesystemManager，說明 Storage::extend() 的註冊、磁碟實例的保留、設定變更後的重新解析，以及不依賴 fake 的驗證。

要從套件提供儲存功能，應將**註冊驅動程式的建立方式**與**建立使用者的磁碟**分開。若設計成僅靠提供者啟動不會開始外部連線或檔案操作，那麼不使用該功能的環境在啟動時也不容易被破壞。

本頁內容依據對 Laravel Framework `v13.37.0` 實作的確認。基本操作請參閱[檔案儲存](/zh-TW/filesystem)，提供者的載入請參閱[套件自動探索](/zh-TW/advanced/package-discovery)。

## 區分驅動程式名稱與磁碟名稱

`Storage::extend()` 的第 1 個參數是**驅動程式名稱**。而 `Storage::disk()` 的參數是在 `filesystems.disks` 中定義的**磁碟名稱**。可以定義多個使用同一驅動程式的磁碟。

| 要素 | 本頁範例 | 角色 |
| - | - | - |
| 驅動程式名稱 | `acme-archive-local` | 建立磁碟的回呼函式的識別碼 |
| 磁碟名稱 | `reports`、`exports` | 設定與已建立實例的識別碼 |
| 磁碟設定 | `root`、`throw` 等 | 建立該磁碟時傳給回呼函式的值 |

`FilesystemManager::resolve()` 會尋找與設定中 `driver` 對應的自訂建立處理，若存在，便優先於內建驅動程式呼叫它。若把 `local` 或 `s3` 當作擴充名稱，就會取代既有的建立處理，因此請選擇套件專屬的名稱。

## 在提供者中只註冊建立方式

以下是為了理解註冊路徑而重用 Laravel 標準本機驅動程式建立處理的最小範例。若只是為了本機儲存，使用一般的 `local` 就已足夠。若要提供自訂後端，請如官方文件的範例般建構 Flysystem 轉接器，並改為以 Laravel 的 `FilesystemAdapter` 包裝的處理。

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

namespace Acme\Archive;

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use InvalidArgumentException;

class ArchiveServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Storage::extend('acme-archive-local', static function (
            Application $app,
            array $config,
        ): FilesystemAdapter {
            if (! is_string($config['root'] ?? null) || $config['root'] === '') {
                throw new InvalidArgumentException('Archive disk requires a root.');
            }

            return $app['filesystem']->createLocalDriver($config);
        });
    }
}
```

此提供者依[一般方式註冊到套件中](/zh-TW/advanced/package-discovery)。為了能在 HTTP、Artisan 與佇列 Worker 中使用，不要以 `runningInConsole()` 限制註冊。此外，只負責註冊擴充的提供者不要設為[延遲提供者](/zh-TW/advanced/deferred-provider)。

回呼函式會收到應用程式以及**待解析磁碟的完整設定**。設定驗證與用戶端建立應在回呼函式內進行，`boot()` 中不要執行檔案操作。範例中的 `createLocalDriver()` 在建立時可能會建立目錄，因此區分註冊時與建立時非常重要。

<Tip>
  `FilesystemManager::extend()` 會將一般閉包的 `$this` 綁定到 Manager。不要假設可以使用提供者的 `$this`，像本範例這樣使用 `static` 閉包與參數 `$app`，可讓相依關係更加明確。
</Tip>

使用者在 `config/filesystems.php` 的 `disks` 陣列中新增設定。

```php theme={null}
'reports' => [
    'driver' => 'acme-archive-local',
    'root' => storage_path('app/private/reports'),
    'throw' => true,
],
'exports' => [
    'driver' => 'acme-archive-local',
    'root' => storage_path('app/private/exports'),
    'throw' => true,
],
```

使用端指定的是磁碟名稱，而不是驅動程式名稱。

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

Storage::disk('reports')->put('summary.txt', 'Monthly summary');
```

<Info>
  本範例只呈現本機儲存的建立路徑。透過 `serve` 提供檔案或提供暫時 URL 不在討論範圍內。即使在自訂轉接器中支援 URL 產生、可見性、唯讀等功能，也請分別確認各自的實作與所需的相依套件，不要認為只要傳入設定鍵就會生效。
</Info>

## 具名磁碟在建立後會被保留

`disk('reports')` 並不會每次都呼叫回呼函式。Manager 的 `get()` 會優先使用內部 `$disks` 陣列中已建立的實例。只有在尚未建立時才會經過 `resolve()`，建立結果會以磁碟名稱為鍵保留下來。

```mermaid theme={null}
flowchart TD
    A["提供者的 boot()"] --> B["以 extend() 註冊建立處理"]
    B --> C["Storage::disk('reports')"]
    C --> D{"reports 已建立？"}
    D -->|是| E["回傳保留的實例"]
    D -->|否| F["讀取 driver，將設定傳給建立處理"]
    F --> G["將建立結果保留為 reports"]
    G --> E
```

因此，即使在同一個行程中執行以下操作，已建立的磁碟也不會自動更新。

* 以 `config()` 變更 `filesystems.disks.reports`。
* 對同一個驅動程式名稱再次呼叫 `Storage::extend()`。

`FilesystemServiceProvider` 也會將 Manager 本身註冊為 singleton。在長時間執行的行程中，不要假設每個請求或 Job 都一定會重新建立磁碟。

## 設定變更後只重新解析需要的磁碟

在同一個行程中變更設定並進行驗證時，要將設定更新與移除保留的磁碟分開處理。

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

$before = Storage::disk('reports');

config(['filesystems.disks.reports.root' => storage_path('app/private/reports-v2')]);
Storage::forgetDisk('reports');

$after = Storage::disk('reports');
```

`forgetDisk()` 會從 Manager 的保留陣列中移除指定名稱。下一次呼叫 `disk()` 時會重新讀取目前的設定。在所確認的版本中，`purge()` 的實作同樣是移除指定名稱，並不會呼叫自訂後端的連線關閉方法。

<Warning>
  像 `$before` 這樣由使用端持有的參照並不會被替換。singleton 服務持有舊磁碟的情況也是如此。不要把 `forgetDisk()` 當作一次更新所有參照或其他行程的機制。
</Warning>

在正式環境變更設定或更新套件時，請依照一般部署流程重新產生設定快取，並重新啟動長時間執行的 Worker。設定快取與此行程內的磁碟保留陣列是兩回事。

### 暫時性設定請使用 build

若不想變更具名的共用設定，可以使用[隨選磁碟](/zh-TW/filesystem)。

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

$disk = Storage::build([
    'driver' => 'acme-archive-local',
    'root' => storage_path('app/private/one-off-export'),
    'throw' => true,
]);
```

`build()` 會以指定設定直接呼叫 `resolve()`，不會將結果加入具名磁碟的保留陣列。雖然需要註冊自訂驅動程式，但每次呼叫都會進入建立處理。即使使用動態儲存位置，也不要設計成可依使用者輸入無限制地選擇儲存位置或連線目標。

## 在 fake 之外另行測試建立路徑

`Storage::fake('reports')` 會透過 Manager 的 `set()` 將該磁碟替換為測試用的本機磁碟。它對應用程式的檔案操作測試很方便，但**並不會驗證套件的自訂建立處理是否正確註冊與執行**。

在[套件的測試環境](/zh-TW/advanced/package-testing)中載入 `ArchiveServiceProvider` 後，分別驗證以下內容。

| 測試 | 確認事項 |
| - | - |
| 一般啟動 | 未使用磁碟時，建立處理內的設定驗證與檔案操作不會開始 |
| 具名磁碟 | `reports` 與 `exports` 分別收到各自的設定，並成為不同的實例 |
| 同名再次取得 | 呼叫兩次 `disk('reports')` 會得到同一個實例 |
| 設定變更 | 僅變更設定時仍是同一實例，`forgetDisk()` 之後成為不同實例 |
| 隨選 | 以相同設定呼叫兩次 `build()` 也會得到不同實例 |
| 無效設定 | 在本範例中，解析缺少 `root` 的磁碟時會擲出例外 |
| 實際讀寫 | 在測試專用目錄中驗證儲存、讀取、刪除，並於結束時清理 |
| 有設定快取 | 在新啟動的驗證用應用程式中，驅動程式同樣已註冊且可解析 |

對於自訂外部後端，要將不使用網路的建立測試，與在明確準備的驗證環境中進行的連線測試分開。不要把 fake 的成功當作與實際服務相容的證據。

## 長期維護檢查清單

* 將驅動程式名稱、設定鍵、回傳值的 API、例外與 `throw` 的處理作為公開規格寫成文件。
* 使用不會與標準驅動程式或其他套件衝突的擴充名稱。
* 不要僅在提供者啟動時就解析或使用磁碟。
* 變更設定時，區分設定快取、Manager 的保留陣列、使用端的參照以及其他行程。
* 針對 Laravel 的每個支援版本，持續驗證建立、重新解析與實際的檔案操作。

## 參考的第一手資料

* [Laravel 官方文件：自訂檔案系統](https://github.com/laravel/docs/blob/12080c72699b/filesystem.md#custom-filesystems)
* [FilesystemManager：disk・build・resolve・forgetDisk・purge・extend](https://github.com/laravel/framework/blob/v13.37.0/src/Illuminate/Filesystem/FilesystemManager.php)
* [FilesystemServiceProvider：Manager 的 singleton 註冊](https://github.com/laravel/framework/blob/v13.37.0/src/Illuminate/Filesystem/FilesystemServiceProvider.php)
* [Storage Facade：透過 fake 替換為本機磁碟](https://github.com/laravel/framework/blob/v13.37.0/src/Illuminate/Support/Facades/Storage.php)
* [Laravel 官方測試：磁碟的建立・移除・自訂閉包](https://github.com/laravel/framework/blob/v13.37.0/tests/Filesystem/FilesystemManagerTest.php)


## Related topics

- [套件的 Artisan 指令註冊與相依性解析](/zh-TW/advanced/package-commands.md)
- [套件的路由註冊與快取](/zh-TW/advanced/package-routes.md)
- [套件的事件監聽器註冊與快取](/zh-TW/advanced/package-events.md)
- [套件的服務解析後掛鉤](/zh-TW/advanced/package-service-resolution.md)
- [將套件診斷資訊加入 about](/zh-TW/advanced/package-about.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.