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

# 套件 Migration 的公開與更新

> 從 Laravel 13 的實作解說 publishesMigrations 與 loadMigrationsFrom 的差異、公開時的時間戳記變更、重新公開的風險，以及如何將結構描述變更交付給既有使用者。

要持續維護使用資料庫的套件，除了首次安裝之外，還需要一套能將變更交付給已存在資料表之使用者的流程。請將 Migration 的公開、執行與執行紀錄視為不同的處理來設計。

本頁以[套件開發的基礎](/zh-TW/advanced/package-development)為前提，解讀 Laravel 13 的 `ServiceProvider`、`VendorPublishCommand` 與 `Migrator`。框架的實作參照 `v13.34.0`。

## 複製後交付，還是從套件直接載入

| 方式 | 服務提供者的處理 | 使用者的操作 | 檔案的管理位置 |
| - | - | - | - |
| 公開 | `publishesMigrations()` | `vendor:publish` 後執行 `migrate` | 應用程式的 `database/migrations` |
| 直接載入 | `loadMigrationsFrom()` | `migrate` | 已安裝的套件內 |

`publishesMigrations()` 只是將複製來源與複製目的地註冊為公開對象。即使啟動服務提供者，也不會複製檔案或執行 SQL。

另一方面，`loadMigrationsFrom()` 會將搜尋路徑註冊至 Migrator。一般的 `migrate` 也會將該路徑下的檔案列為對象，但僅啟動服務提供者並不會執行它們。

```mermaid theme={null}
flowchart TD
    A["套件的服務提供者"] --> B["publishesMigrations()<br>註冊複製來源與複製目的地"]
    B --> C["vendor:publish<br>複製至應用程式"]
    C --> E["migrate<br>執行尚未執行的檔案"]
    A --> D["loadMigrationsFrom()<br>加入 Migrator 的搜尋路徑"]
    D --> E
    E --> F["將已執行的檔案名稱<br>記錄至 migrations 資料表"]
```

若設計上讓使用者在執行前調整資料表名稱或欄位，公開方式是可考慮的選項。若由套件管理結構描述，且不預期使用者編輯檔案，也可以考慮直接載入方式。以下兩個服務提供者範例是彼此替代的方案。

<Warning>
  請避免同時公開又直接載入同一個 Migration 的設計。若公開時時間戳記被變更，複製來源與複製目的地會被視為不同的執行紀錄，同一個建立資料表的處理可能會被執行兩次。
</Warning>

## 實作公開方式

加上套件專屬的標籤，讓使用者能將其與其他資源區分並公開。

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

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->publishesMigrations([
            __DIR__.'/../database/migrations' => database_path('migrations'),
        ], 'courier-migrations');
    }
}
```

首次安裝時，指定目標服務提供者與標籤進行複製，確認內容後再執行。同時指定兩者時，Laravel 會選出屬於該服務提供者、且帶有該標籤的公開對象。

```bash theme={null}
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider" --tag=courier-migrations
php artisan migrate:status
php artisan migrate
```

### 時間戳記的變更與設定有關

官方文件說明了公開時會將 Migration 的時間戳記更新為目前日期時間的行為。不過，在 `ServiceProvider::publishesMigrations()` 的實作中，只有在 `database.migrations.update_date_on_publish` 啟用時，才會將複製來源加入時間戳記更新的對象。取得此設定時的預設值為 `false`。

Laravel 13 標準應用程式的 `config/database.php` 中有以下設定。沿用舊結構的應用程式，也請確認此設定是否存在。

```php theme={null}
'migrations' => [
    'table' => 'migrations',
    'update_date_on_publish' => true,
],
```

此外，`VendorPublishCommand` 會在與已註冊之複製來源的實際路徑一致，且複製目的地的名稱帶有 `YYYY_MM_DD_HHMMSS_` 格式時改寫日期時間。以指令開始的時刻為基準，每個對象檔案依序加 1 秒。若名稱中沒有此格式，該處理不會補上日期時間。

```text theme={null}
複製來源:
2026_09_01_000000_create_courier_deliveries_table.php

公開後的範例:
2026_10_02_120001_create_courier_deliveries_table.php
```

上述公開後的日期時間僅為說明用。實際的檔案名稱會依公開的時刻而異。

<Info>
  時間戳記更新同時取決於套件的註冊與使用端應用程式的設定。請勿從套件的服務提供者一律變更此設定，而應在安裝步驟中記載此前提。若使用設定快取，變更設定後也需要重新建置。
</Info>

## 重新公開並不是「只新增尚未執行的項目」

`vendor:publish` 不會檢查資料庫的執行紀錄。此外，在 `v13.34.0` 的複製處理中，既有檔案的檢查是針對**時間戳記變更前**的複製目的地進行。即使是目錄公開，也會先確認複製目的地是否存在與複製來源相同的相對路徑，之後才改寫日期時間。

因此，若首次公開時日期時間已被變更，且應用程式端沒有與複製來源同名的檔案，再次公開同一個標籤時，可能會新增一個不同日期時間的檔案。請不要認為只要不加 `--force` 就一定能防止重複。

| 條件 | 重新公開時需注意的事項 |
| - | - |
| 日期時間更新已啟用，且變更前的複製目的地不存在 | 可能會新增不同日期時間的副本 |
| 日期時間更新已停用，且複製目的地同名 | 通常會略過既有檔案 |
| 指定 `--force` | 若符合複製條件且日期時間更新已啟用，也可能產生不同名稱的檔案 |
| 指定 `--existing` | 由於檢查的是變更前的複製目的地是否存在，即使只有已變更日期時間的檔案，也不一定會成為對象 |

### 是否已執行是依檔案名稱判斷

`Migrator::getMigrationName()` 會回傳從檔案基本名稱去除 `.php` 後的字串。判斷是否尚未執行時，會將此名稱與執行紀錄比對。並不是依 PHP 內容或資料表名稱是否相同來判斷。

```text theme={null}
2026_10_02_120001_create_courier_deliveries_table
2026_10_03_090001_create_courier_deliveries_table
```

這兩者是不同的 Migration 名稱。即使前者已執行，僅憑該紀錄，後者也不會被視為已執行。

<Warning>
  請勿在每次更新套件時無條件重新執行首次安裝用的公開指令，也不要將 `--force` 作為標準步驟。這不僅會覆寫對已公開檔案的編輯，還可能因日期時間變更而新增重複的處理。
</Warning>

## 實作直接載入方式

若希望直接將套件內的 Migration 作為執行對象，請註冊搜尋路徑。此方式不會額外加入公開同一檔案的處理。

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

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
    }
}
```

`loadMigrationsFrom()` 會在 Migrator 被解析時呼叫 `path()`。`Migrator::path()` 會去除重複的搜尋路徑，`getMigrationFiles()` 則以 Migration 名稱作為鍵值整理找到的檔案，並依名稱排序。

使用者更新套件後，新檔案會成為下一次 `migrate` 的對象。請勿變更既有檔案的名稱，新的結構描述變更請新增檔案。為避免與其他套件衝突，請像 `create_courier_deliveries_table` 一樣在名稱中包含功能名稱。同名的檔案會成為相同的鍵值，並不會各自獨立執行。

<Warning>
  從公開方式改為直接載入方式，並不只是改寫服務提供者而已。若使用者的執行紀錄是以公開時的名稱記錄，就會與套件端的原始名稱不一致。需要一套涵蓋既有使用者的執行紀錄、已公開檔案與回滾的遷移步驟。
</Warning>

## 將結構描述變更交付給既有使用者

例如要在配送資料表加入追蹤編號時，不要編輯已公開的 `create_courier_deliveries_table`，而是新增一個用於變更的新檔案。即使編輯既有的建立用 Migration，已執行過的使用者也不會套用該變更。

```php database/migrations/2026_10_02_000000_add_tracking_code_to_courier_deliveries_table.php theme={null}
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('courier_deliveries', function (Blueprint $table) {
            $table->string('tracking_code')->nullable();
        });
    }

    public function down(): void
    {
        Schema::table('courier_deliveries', function (Blueprint $table) {
            $table->dropColumn('tracking_code');
        });
    }
};
```

由於這是對已有資料列的資料表新增欄位的範例，此處設為 nullable。若需要改為必填或回填資料，請另外設計相應的步驟與執行順序。

在公開方式中，請準備一套與使用者已公開的檔案比對、**只交付本次新增檔案**的更新步驟。也可以為新檔案設立專用的公開標籤，但若日期時間更新已啟用，重複執行該標籤時也需要同樣的注意。不要讓更新步驟只是重新執行首次安裝用的標籤。

在直接載入方式中，更新後的程式碼能偵測到新檔案。無論採用哪種方式，僅有檔案存在並不會變更資料庫，因此請在發行說明中明確記載需要執行 Migration。

## 發行前的確認事項

除了套件的資料庫測試之外，也請在使用端應用程式中確認公開與更新的步驟。在測試中僅直接載入 Migration，並不等於驗證了檔案名稱會改變的公開方式。

* 能在空的資料庫上進行首次安裝，並建立所需的資料表。
* 從舊版本的資料庫與執行紀錄進行更新，只會套用新的變更。
* 確認重複執行同一個公開指令時的檔案清單，更新步驟不會產生重複。
* 步驟已考量日期時間更新的啟用與停用，以及對已公開檔案的編輯。
* 確認新 Migration 的回滾，以及與應用程式其他 Migration 之間的執行順序。

## 相關頁面

<Columns cols={2}>
  <Card title="Migration" icon="database" href="/zh-TW/migrations">
    確認結構描述定義、執行紀錄與回滾的基本概念。
  </Card>

  <Card title="套件的測試" icon="flask" href="/zh-TW/advanced/package-testing">
    測試套件的服務提供者與資料庫。
  </Card>

  <Card title="套件設定的合併與快取" icon="sliders" href="/zh-TW/advanced/package-config-merging">
    確認考量使用者設定與設定快取的更新步驟。
  </Card>

  <Card title="版本相容性管理" icon="code-branch" href="/zh-TW/advanced/package-versioning">
    將更新步驟與相容性變更連結至發行方針。
  </Card>
</Columns>

## 參考的一手資料

* [Laravel 官方文件：套件的 Migration](https://github.com/laravel/docs/blob/156fc7fde114548640e13c39aa79b991291c3f91/packages.md#migrations)
* [ServiceProvider：公開對象與搜尋路徑的註冊](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Support/ServiceProvider.php)
* [VendorPublishCommand：複製與時間戳記變更](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Console/VendorPublishCommand.php)
* [Migrator：檔案的偵測與尚未執行的判斷](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Database/Migrations/Migrator.php)
* [Laravel 13 標準應用程式：資料庫設定](https://github.com/laravel/laravel/blob/06d016a364a37430eec9cbc52209adce3d7667ce/config/database.php)


## Related topics

- [Laravel 套件開發](/zh-TW/advanced/package-development.md)
- [套件設定的合併與快取](/zh-TW/advanced/package-config-merging.md)
- [Laravel Fetch Metadata](/zh-TW/packages/laravel-fetch-metadata.md)
- [BlueskyManager 與 HasShortHand](/zh-TW/packages/laravel-bluesky/bluesky-manager.md)
- [Session 恢復](/zh-TW/packages/laravel-copilot-sdk/resume.md)


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