> ## 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 的 ServiceProvider 实现，讲解浅层合并与递归替换的区别、数字键数组的陷阱，以及兼顾配置缓存的包维护方法。

即使你为包添加了新的配置项，这些新项也不会自动写入用户之前发布的配置文件中。要在不破坏已发布配置的前提下补充默认值，你需要同时设计数组的合并策略和配置缓存。

本页将阅读 Laravel 13 的 `ServiceProvider`，整理如何将配置作为包的公开 API 进行维护。本页以[Laravel 包开发](/zh-CN/advanced/package-development)为前提，并使用 `laravel/framework` 的 `v13.34.0` 来确认实现。

## 发布与合并是不同的处理

`publishes()` 注册复制的源路径和目标路径。在 `vendor:publish` 复制文件之前，用户的 `config` 目录不会发生变化。而 `mergeConfigFrom()` 则是在启动时更新配置仓库，并不会修改文件本身。

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

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->mergeConfigFrom(
            __DIR__.'/../config/courier.php', 'courier'
        );
    }

    public function boot(): void
    {
        $this->publishes([
            __DIR__.'/../config/courier.php' => config_path('courier.php'),
        ], 'courier-config');
    }
}
```

用户只在需要时才发布配置文件。即使不发布，在没有缓存的常规启动中，也会通过 `register()` 中的合并使用默认值。

```bash theme={null}
php artisan vendor:publish --tag=courier-config
```

<Warning>
  如果在更新步骤中使用 `--force` 重新发布配置文件，会覆盖用户编辑过的内容。如果只是添加新的配置项，请优先采用补充默认值并说明变更内容的方式。
</Warning>

## mergeConfigFrom() 只合并顶层

`ServiceProvider::mergeConfigFrom()` 会先传入包的配置，再传入应用的现有配置，然后执行 `array_merge()`。对于相同的字符串键，应用侧的值优先。

以下示例仅使用数组重现了框架的合并处理。

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

$defaults = [
    'enabled' => true,
    'transport' => [
        'timeout' => 10,
        'retries' => 3,
    ],
];

$overrides = [
    'transport' => [
        'timeout' => 30,
    ],
];

$config = array_merge($defaults, $overrides);

var_export($config);
```

```text theme={null}
array (
  'enabled' => true,
  'transport' =>
  array (
    'timeout' => 30,
  ),
)
```

顶层的 `enabled` 会被补充，但 `transport` 整个数组会被替换，`transport.retries` 不会保留。关键在于：如果用户已经发布了旧的 `transport` 数组，即使你在同一个数组中添加新键，这些键也不会被补充。

## 使用 replaceConfigRecursivelyFrom() 补充嵌套配置

Laravel 13 的 `ServiceProvider` 中还有一个 protected 方法 `replaceConfigRecursivelyFrom()`。它以相同的顺序执行 `array_replace_recursive()`。

如果你希望配置 API 能够单独覆盖嵌套的字符串键，可以按如下方式修改提供者的 `register()`。对于同一个配置键，无需与前面的 `mergeConfigFrom()` 同时使用。

```php theme={null}
public function register(): void
{
    $this->replaceConfigRecursivelyFrom(
        __DIR__.'/../config/courier.php', 'courier'
    );
}
```

使用前面的 `$defaults` 和 `$overrides`，结果如下。

```php theme={null}
$config = array_replace_recursive($defaults, $overrides);

var_export($config);
```

```text theme={null}
array (
  'enabled' => true,
  'transport' =>
  array (
    'timeout' => 30,
    'retries' => 3,
  ),
)
```

| 配置约定 | 选择的处理 | 注意事项 |
| - | - | - |
| 嵌套数组由用户整体指定 | `mergeConfigFrom()` | 数组内未指定的键不会被补充 |
| 嵌套项由用户仅指定一部分 | `replaceConfigRecursivelyFrom()` | 数字键列表也会被递归替换 |

<Info>
  `replaceConfigRecursivelyFrom()` 是本页所确认的 Laravel 13 源代码中存在的方法。请将其与官方包开发文档介绍的 `mergeConfigFrom()` 区分开来，并在确认对应框架版本的实现后再使用。
</Info>

### 数字键列表不会被整体替换

递归替换并不是"将数组全部替换为用户的值"的处理。对于数字键，它同样替换相同键的值，并保留用户未指定的键。

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

$defaults = ['channels' => ['mail', 'database']];
$overrides = ['channels' => ['slack']];

$config = array_replace_recursive($defaults, $overrides);

var_export($config['channels']);
```

```text theme={null}
array (
  0 => 'slack',
  1 => 'database',
)
```

即使用户只指定了 `['slack']`，`database` 仍会保留。另外，即使传入 `['channels' => []]`，默认列表也不会变为空。对于通知渠道或中间件等整体指定列表才有意义的配置，请特别注意。

如果你的包包含此类配置，请从配置结构入手考虑，例如将列表和可部分覆盖的关联数组拆分到不同的顶层键中，并使用浅层合并。对于已经发布的包，更改合并方式会导致同一个配置文件的行为发生变化，因此请不要将其仅仅视为实现层面的替换。

## 配置缓存中保存的是合并后的值

当应用实现了 `CachesConfiguration` 且 `configurationIsCached()` 为 `true` 时，这两个方法都会跳过合并处理。在常规的 Laravel 应用中，存在配置缓存的启动就属于这种情况。

`ConfigCacheCommand` 会删除旧的配置缓存，启动一个新的应用并获取整个配置仓库。在这次启动中，提供者的配置会被合并，结果保存到缓存文件中。之后的启动中，`LoadConfiguration` 会读取这些值。

```mermaid theme={null}
flowchart TD
    A["php artisan config:cache"] --> B["删除旧的配置缓存"]
    B --> C["启动新的应用"]
    C --> D["读取配置文件<br>在提供者中合并"]
    D --> E["保存整个配置仓库"]
    E --> F["之后的启动使用已保存的配置<br>两个合并方法均被跳过"]
```

因此，即使更新包后默认值或合并方式发生了变化，继续使用旧缓存的应用也不会反映这些变化。在使用配置缓存的部署中，请使用更新后的代码重新构建缓存。

```bash theme={null}
php artisan config:cache
```

如果你想在开发过程中恢复为从文件重新读取的状态，请使用 `php artisan config:clear`。不要自行决定缓存的生成位置，请交由 Laravel 的命令进行管理。

<Warning>
  请不要在配置文件中定义 Closure，因为 `config:cache` 无法正确序列化它们。如果需要传递回调，请在配置中放置类名等内容，并在提供者中注册实际的服务。
</Warning>

## 发布配置变更前的检查

在包的测试中，不仅要处理未发布的配置，还要将旧版本遗留下来的配置作为输入。

* 即使未发布配置，也能获取所需的默认值。
* 对于旧的已发布配置，用户的值优先，且新项按设计被补充。
* 对于嵌套数组、数字键列表和空数组，覆盖约定保持不变。
* 在使用该包的应用中 `config:cache` 能够成功执行，并且在使用缓存的另一次启动中得到相同的配置。

请在发布说明中记录新键的默认值以及所需的缓存重建。对于键的删除、重命名或合并方式的变更，请将与用户已发布配置的兼容性一并纳入考量。

## 相关页面

<Columns cols={2}>
  <Card title="包的测试" icon="flask" href="/zh-CN/advanced/package-testing">
    注册服务提供者，验证配置和服务的行为。
  </Card>

  <Card title="版本兼容性管理" icon="code-branch" href="/zh-CN/advanced/package-versioning">
    将公开 API 的变更与发布策略和持续维护联系起来。
  </Card>
</Columns>

## 参考的一手资料

* [Laravel 官方文档：包配置](https://github.com/laravel/docs/blob/13.x/packages.md#configuration)
* [ServiceProvider：合并与发布的实现](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Support/ServiceProvider.php)
* [ConfigCacheCommand：生成配置缓存](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Console/ConfigCacheCommand.php)
* [LoadConfiguration：读取配置](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php)
* [ConfigClearCommand：删除配置缓存](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Console/ConfigClearCommand.php)


## Related topics

- [Laravel 包开发](/zh-CN/advanced/package-development.md)
- [进阶主题](/zh-CN/advanced/index.md)
- [缓存](/zh-CN/cache.md)
- [2026 年 6 月 Laravel 更新](/zh-CN/blog/changelog/202606.md)
- [配置](/zh-CN/configuration.md)


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