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

# 包缓存与 optimize 的集成

> 使用 Laravel 13 的 optimizes()，将包自有缓存的生成与删除集成到部署流程中。基于实现讲解注册键、排除指定、执行顺序以及失败时的退出码。

如果你的包需要预先生成自有的元数据，仅仅让用户在部署步骤中添加一条专用命令，很容易在升级时漏掉执行。使用 `ServiceProvider::optimizes()`，你可以把生成与删除命令集成到 Laravel 的 `optimize` 和 `optimize:clear` 中。

本页以[Laravel 包开发](/zh-CN/advanced/package-development)为前提，基于 Laravel Framework `v13.35.0` 确认实现。本页讨论的不是缓存文件的格式，而是注册与运维方面的约定。

## 区分命令注册与任务注册

`commands()` 用于注册可以从 Artisan 调用的命令类。`optimizes()` 则是另一项处理，它把已经可以执行的命令名称注册为优化任务。只调用后者并不会注册命令类。

下面的示例假设包中已经实现了 `CacheMetadataCommand` 和 `ClearMetadataCommand`，它们的 `$signature` 分别为 `courier:cache` 和 `courier:clear-cache`。

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

namespace Acme\Courier;

use Acme\Courier\Console\Commands\CacheMetadataCommand;
use Acme\Courier\Console\Commands\ClearMetadataCommand;
use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            $this->commands([
                CacheMetadataCommand::class,
                ClearMetadataCommand::class,
            ]);

            $this->optimizes(
                optimize: 'courier:cache',
                clear: 'courier:clear-cache',
                key: 'acme-courier',
            );
        }
    }
}
```

`optimizes()` 的参数全部可为 null，因此也可以只注册生成命令或只注册删除命令。不过，你务必要告诉用户通过哪个步骤让已生成的缓存失效。

## 注册键也是面向用户的约定

`ServiceProvider` 会把生成命令保存在静态数组 `$optimizeCommands` 中，把删除命令保存在 `$optimizeClearCommands` 中。两者都以 `key` 作为数组键。

如果省略 `key`，会根据 Provider 的类名生成名称。例如 `CourierServiceProvider` 会生成 `courier`。由于只使用类名，位于不同命名空间的同名 Provider 也可能发生冲突。

对同一个键重新注册时，对应一侧的命令会被后注册的值覆盖。如果需要注册多个任务，请指定不同的键。此外，应避免使用 `config`、`routes` 等 Laravel 标准任务的键，因为在合并标准任务与包任务时，相同的字符串键同样会被覆盖。

<Tip>
  请像 `acme-courier` 这样显式指定能够识别包的键，并在各个版本之间保持不变。键会成为任务的显示名称，也是用户在 `--except` 中指定的值。
</Tip>

## 在标准任务之后执行

在所确认的实现中，两个命令都会先把包的注册数组展开到标准任务数组中，然后依次调用。只要使用不冲突的键，包任务就会追加在标准任务之后。

| 命令 | 标准任务的执行顺序 | 之后的处理 |
| - | - | - |
| `optimize` | `config:cache` → `event:cache` → `route:cache` → `view:cache` | 已注册的生成命令 |
| `optimize:clear` | `config:clear` → `cache:clear` → `clear-compiled` → `event:clear` → `route:clear` → `view:clear` | 已注册的删除命令 |

```mermaid theme={null}
flowchart TD
    A["Provider 的 boot()"] --> B["用 commands() 注册 Artisan 命令"]
    A --> C["用 optimizes() 注册键和命令名称"]
    B --> D["php artisan optimize"]
    C --> D
    D --> E["缓存标准的配置、事件、路由、视图"]
    E --> F["执行 courier:cache"]
    C --> G["php artisan optimize:clear"]
    G --> H["删除标准缓存"]
    H --> I["执行 courier:clear-cache"]
```

基于这一顺序，生成命令不应重新构建其他标准缓存，而只生成包自己拥有的数据。不要把 `optimizes()` 当作控制多个包之间依赖顺序的 API。如果需要严格的顺序，请显式地依次排列专用命令。

<Warning>
  `optimize:clear` 也包含 `cache:clear`，会删除默认缓存存储中的数据。如果只想清除包专用的缓存，请直接执行 `courier:clear-cache`。包的删除命令本身也应设计为不 flush 整个共享存储，只删除自己拥有的键或文件。
</Warning>

## 通过键或命令名称排除

两个命令的 `--except` 都接受以逗号分隔的值。每个值会去掉前后的空白，与任务的键或命令名称匹配的任务会被排除。

```bash theme={null}
php artisan optimize --except=acme-courier
php artisan optimize --except=courier:cache
php artisan optimize:clear --except=acme-courier,cache
```

前两条排除的是同一个生成任务。第三条排除包的删除任务以及标准的 `cache:clear`。`cache` 是任务的键，并不是包专用的名称。

排除指定只对当次执行生效。它不是禁用 Provider 注册的设置，也不会自动删除之前生成的包缓存。

## 区分任务的 FAIL 与父命令的退出码

`OptimizeCommand` 和 `OptimizeClearCommand` 会用 `callSilently()` 调用每个任务，并把退出码是否为 `0` 传给任务显示。正常情况下子命令的输出不会显示，因此要排查原因时，请直接执行专用命令。

在 Laravel `v13.35.0` 中，两个 `handle()` 即使子命令返回非零值，也不会把该值作为父命令的返回值。即使屏幕上显示 `FAIL`，循环也会继续，只要没有异常，父命令的退出码就是 `0`。另一方面，抛出的异常会在任务显示组件中被重新抛出，因此不会表现出同样的继续执行行为。

<Warning>
  不要仅凭 `php artisan optimize` 的退出码为 `0` 就判断包缓存已成功生成。此行为基于所确认版本的实现，因此在更新所支持的 Laravel 版本时也要重新确认。
</Warning>

如果包的生成是部署的必要条件，请采用可以直接检查子命令退出码的步骤。例如，下面的示例把包任务从批量执行中排除，并在标准任务之后单独直接执行一次。

```bash theme={null}
php artisan optimize --except=acme-courier && php artisan courier:cache
```

此示例会把 `courier:cache` 的失败反映到退出码上，但不会汇总标准任务的非零退出。如果部署需要严格检测标准任务，请逐条执行所需命令并检查各自的退出码。

## 设计并验证经得起升级的缓存

除了注册之外，还要事先明确命令与读取端各自的职责。

* 生成操作在重复执行时，相同的输入应得到相同的状态，并且中途失败时不能启用不完整的数据。
* 删除操作在缓存不存在时也应正常完成，且不能删除用户已发布的配置或持久化数据。
* 生成失败的命令应报告错误并返回非零值。读取端也不应无条件地把损坏的缓存视为正常。
* 更改缓存格式后，要告知用户需要重新生成，并考虑重启长时间运行的进程。

除了包本身的测试，还要在使用该包的应用中确认以下内容。由于注册数组是静态的，在同一进程内的测试之间，也要注意注册状态会被带到下一个测试中。

| 要确认的操作 | 完成条件 |
| - | - |
| 直接执行专用的生成与删除命令 | 正常时返回 `0`，生成失败时返回非零值。连续执行两次删除也能成功 |
| 执行 `optimize` / `optimize:clear` | 包任务各被调用一次，生成与删除后的状态正确 |
| 用键和命令名称指定 `--except` | 只有目标任务不被执行 |
| 让生成命令以非零值退出 | 能够区分 `FAIL` 显示与所确认版本中父命令的退出码 |
| 升级包后重新生成 | 使用新的代码和配置生成，不会继续读取旧格式 |

## 相关页面

<Columns cols={2}>
  <Card title="包配置的合并与缓存" icon="sliders" href="/zh-CN/advanced/package-config-merging">
    确认已发布配置的补充与配置缓存重建之间的关系。
  </Card>

  <Card title="使用 Orchestra Testbench 测试 Laravel 包" icon="flask" href="/zh-CN/advanced/package-testing">
    在测试环境中注册 Provider 和 Artisan 命令。
  </Card>
</Columns>

## 参考的一手资料

* [Laravel 官方文档：Optimize commands](https://github.com/laravel/docs/blob/13.x/packages.md#optimize-commands)
* [ServiceProvider：optimizes() 与注册键](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Support/ServiceProvider.php)
* [OptimizeCommand：任务与排除处理](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Foundation/Console/OptimizeCommand.php)
* [OptimizeClearCommand：删除任务与执行顺序](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Foundation/Console/OptimizeClearCommand.php)
* [Command：handle() 的返回值与退出码](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Console/Command.php)
* [Task：结果显示与异常的重新抛出](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Console/View/Components/Task.php)


## Related topics

- [Laravel 包开发](/zh-CN/advanced/package-development.md)
- [进阶主题](/zh-CN/advanced/index.md)
- [缓存](/zh-CN/cache.md)
- [包自动发现的内部结构](/zh-CN/advanced/package-discovery.md)
- [引擎 API 集成应用开发指南 - VOICEVOX for Laravel](/zh-CN/packages/laravel-voicevox/app-guide.md)


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