> ## 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 13 的实现，讲解 publishesMigrations 与 loadMigrationsFrom 的区别、发布时的时间戳变更、重新发布的风险，以及如何将架构变更交付给现有用户。

要持续维护使用数据库的包，不仅需要考虑首次安装，还需要一套将变更交付给已经存在数据表的用户的流程。请将迁移的发布、执行和执行记录作为彼此独立的处理来设计。

本页以[包开发基础](/zh-CN/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>
  请避免既发布同一个迁移又让其被直接加载的设计。如果发布时时间戳发生变化，复制源和复制目标会被视为不同的执行记录，同一个建表处理可能会被执行两次。
</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
```

### 时间戳的变更与配置相关

官方文档说明，发布时会将迁移的时间戳更新为当前日期时间。但在 `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
```

这两个是不同的迁移名称。即使前者已执行，仅凭该记录，后者也不会被视为已执行。

<Warning>
  请不要在每次更新包时都无条件地重新执行首次安装用的发布命令，也不要将 `--force` 作为标准步骤。这不仅会覆盖对已发布文件的编辑，还可能因日期时间变更而添加重复的处理。
</Warning>

## 实现直接加载方式

如果希望将包内的迁移直接作为执行对象，请注册搜索路径。在这种方式下，不要再添加发布相同文件的处理。

```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()` 则以迁移名称作为键存储找到的文件，并按名称排序。

用户更新包后，新文件会成为下一次 `migrate` 的执行对象。请不要更改已有文件的名称，而是为新的架构变更添加新文件。为避免与其他包冲突，请像 `create_courier_deliveries_table` 这样在名称中包含功能名。同名文件会成为同一个键，两者并不会各自独立执行。

<Warning>
  从发布方式改为直接加载方式，不只是改写提供者那么简单。如果用户的执行记录是以发布时的名称记录的，就不会与包中的原始名称一致。需要一套涵盖现有用户的执行记录、已发布文件和回滚的迁移步骤。
</Warning>

## 将架构变更交付给现有用户

例如要为配送表添加追踪编号时，不要编辑已发布的 `create_courier_deliveries_table`，而是添加一个用于变更的新文件。即使编辑已有的建表迁移，对于已经执行过的用户，这一变更也不会被执行。

```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。如果需要设为必填或回填数据，请另行设计相应步骤和执行顺序。

在发布方式下，请准备一套与用户已发布文件进行比对、**只交付本次新增文件**的更新步骤。也可以为新文件设置专用的发布标签，但如果启用了日期时间更新，反复执行该标签时同样需要注意。不要把更新步骤设计成仅仅重新执行首次安装用的标签。

在直接加载方式下，更新后的代码可以检测到新文件。无论采用哪种方式，仅仅存在文件并不会改变数据库，因此请在发布说明中明确写出需要执行迁移。

## 发布前需要确认的事项

除了包的数据库测试之外，还请在使用方应用程序中确认发布和更新的步骤。仅在测试中直接加载迁移，并不等于验证了文件名会发生变化的发布方式。

* 在空数据库上进行首次安装，能够创建所需的表。
* 从旧版本的数据库和执行记录进行更新时，只应用新的变更。
* 确认重复执行相同发布命令时的文件列表，更新步骤不会产生重复。
* 步骤已考虑日期时间更新的启用与禁用，以及对已发布文件的编辑。
* 确认新迁移的回滚，以及与应用程序中其他迁移的执行顺序。

## 相关页面

<Columns cols={2}>
  <Card title="迁移" icon="database" href="/zh-CN/migrations">
    了解架构定义、执行记录和回滚的基础知识。
  </Card>

  <Card title="包测试" icon="flask" href="/zh-CN/advanced/package-testing">
    测试包的服务提供者和数据库。
  </Card>

  <Card title="包配置的合并与缓存" icon="sliders" href="/zh-CN/advanced/package-config-merging">
    了解考虑用户配置和配置缓存的更新步骤。
  </Card>

  <Card title="版本兼容性管理" icon="code-branch" href="/zh-CN/advanced/package-versioning">
    将更新步骤和兼容性变更与发布策略关联起来。
  </Card>
</Columns>

## 参考的一手资料

* [Laravel 官方文档：包的迁移](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-CN/advanced/package-development.md)
- [进阶主题](/zh-CN/advanced/index.md)
- [Mercure 广播驱动内部机制](/zh-CN/advanced/mercure-broadcasting.md)
- [2026 年 7 月 Laravel 更新](/zh-CN/blog/changelog/202607.md)
- [包配置的合并与缓存](/zh-CN/advanced/package-config-merging.md)


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