> ## 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 的实现，讲解带命名空间视图的查找顺序、已发布 Blade 模板的维护，以及视图缓存与查找结果缓存的区别。

对于允许用户自定义页面或邮件模板的包，不仅需要发布视图，还需要一套在保留已发布文件的同时进行更新的约定。即使修改了包中的 Blade 文件，使用方应用程序也不一定在渲染该文件。

本页以[包开发基础](/zh-CN/advanced/package-development)为前提，将视图的选择、文件的发布和缓存分开讨论。官方文档参考的是 Laravel 13，框架实现参考的是最新发布版本 `v13.34.0`。

## 注册与发布是不同的处理

`loadViewsFrom()` 会为命名空间注册查找路径。`publishes()` 注册复制源和复制目标，实际的复制由 `vendor:publish` 完成。在下面的示例中，即使不发布也可以使用 `courier::deliveries.show`。

```php src/CourierServiceProvider.php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');

        $this->publishes([
            __DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
        ], 'courier-views');
    }
}
```

包中的文件放在 `resources/views/deliveries/show.blade.php`。视图名中的点号在查找时会被转换为目录分隔符。

```php theme={null}
return view('courier::deliveries.show', [
    'trackingCode' => 'TRACK-001',
]);
```

```blade resources/views/deliveries/show.blade.php theme={null}
<p>追踪编号: {{ $trackingCode }}</p>
```

视图的命名空间与 Composer 的包名和 PHP 的命名空间是不同的。在这里，`loadViewsFrom()` 第二个参数指定的 `courier` 构成了视图引用和覆盖目录的约定。

## 按文件查找覆盖位置

`ServiceProvider::loadViewsFrom()` 会在 `view` 被解析时，依次检查配置中的 `view.paths`。如果各路径下存在 `vendor/courier` 目录，就将该目录添加到命名空间中，最后再添加包的路径。

`FileViewFinder` 会依次查找该命名空间的路径，并返回第一个找到的文件。在使用标准 `resources/views` 的配置下，顺序如下。

```mermaid theme={null}
flowchart TD
    A["courier::deliveries.show"] --> B["查找 resources/views/vendor/courier/<br>deliveries/show.blade.php"]
    B --> C{"文件存在?"}
    C -->|是| D["使用应用程序的视图"]
    C -->|否| E["查找包的 resources/views/<br>deliveries/show.blade.php"]
    E --> F{"文件存在?"}
    F -->|是| G["使用包的视图"]
    F -->|否| H["视图未找到异常"]
```

这并不是整个目录的切换。即使用户只覆盖了 `deliveries/show.blade.php`，其他未覆盖的视图仍会从包中加载。

| 使用方应用程序的状态 | 选中的视图 |
| - | - |
| 没有覆盖文件 | 包中的文件 |
| 存在相同相对路径的覆盖文件 | 应用程序中的文件 |
| 仅移除了覆盖文件 | 重新启动后恢复为包中的文件 |
| 两处都没有目标文件 | `View [...] not found.` 异常 |

<Info>
  在存在多个 `view.paths` 的配置中，覆盖位置也可能有多个。`resource_path('views/vendor/courier')` 只是本示例中的发布目标，并不是将查找范围固定在该位置的处理。请使用包专属的命名空间，避免多个提供者向同一名称添加路径的设计。
</Info>

## 只自定义需要的视图

用户可以通过以下命令复制模板。指定提供者和标签，以免牵连其他资源。

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

在这种注册方式下，整个视图目录都会被发布。如果不需要全部覆盖，可以在确认内容后只保留要自定义的文件，或者只将需要的文件手动复制到相同的相对路径。这是因为未编辑的副本只要存在，也会被视为覆盖。

<Warning>
  已发布的模板不会随包的更新自动同步。在旧副本优先的状态下只修改包中的文件，该视图的变更不会生效。显示问题的修复和表单变更等也需要与覆盖文件进行比较。
</Warning>

### 重新发布不会合并差异

`VendorPublishCommand` 通常在存在同名发布目标文件时跳过复制。`--force` 会覆盖现有文件。此外，`--existing` 也是"覆盖已发布文件"的选项，并不是保留用户编辑的模式。

| 操作 | 对视图文件的影响 |
| - | - |
| 普通的重新发布 | 保留现有文件，复制不存在的目标文件 |
| 带 `--force` 发布 | 连现有的自定义内容也会被覆盖 |
| 带 `--existing` 发布 | 仅覆盖发布目标中已存在的目标文件 |

无论哪种方式，都不是对旧版、新版和用户编辑进行比较后的合并。也不会自动从发布目标中删除已从包中移除的视图。请不要将更新步骤简化为"只需重新发布同一标签"。

## 将视图也作为公开 API 进行维护

不仅是视图名，接收的数据和引用的组件也会影响用户的自定义内容。例如，如果在新版中将 `trackingCode` 改为其他变量名，保留旧模板的用户将无法从新代码中获得所需的值。

发布前，请确认以下约定。

* 不要随意更改命名空间和 `deliveries.show` 等视图名。
* 记录传递的变量、类型以及必填与可选的区别。
* 将 `@include` 和 `@extends` 的引用目标、Blade 组件的 props 也纳入变更范围。
* 在发布说明中写明修改过的视图，以及需要应用到已发布旧版模板的变更。

面向用户，应提供比较旧版与新版包视图、并将所需变更手动合并到已自定义文件中的步骤。对于不再需要覆盖的文件，先通过备份或版本控制保存变更后再移除，即可恢复为包中的视图。

## Blade 缓存不会更新覆盖文件

`view:cache` 会将 Blade 模板预编译为 PHP。`ViewCacheCommand` 首先执行 `view:clear`，然后收集普通视图路径和注册到命名空间的路径来查找编译对象。

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

该处理不会改写已发布的 Blade 文件，也不会改变视图的查找优先级。如果存在旧的覆盖文件，即使重建缓存，该文件仍会被继续选中。部署时，请在完成代码和覆盖文件的更新之后再进行编译。

在开发过程中，如果想删除已编译文件并重新渲染，请使用以下命令。

```bash theme={null}
php artisan view:clear
```

如果启用了常规的时间戳检查，Blade 编译器会比较源文件与已编译文件的修改时间。但由于也存在禁用时间戳检查的配置，请不要将部署时的重建完全交给自动判断。

### 与查找结果缓存区分开

`FileViewFinder::find()` 会将找到的路径保存到该 Finder 实例的 `$views` 数组中。此外，覆盖目录的存在性检查是在 `loadViewsFrom()` 的回调中进行的。即使在启动后添加了新目录，也不会自动加入已注册的查找路径。

| 管理对象 | 作用 | 更新时的思路 |
| - | - | - |
| 已发布的 Blade 文件 | 用户的自定义内容 | 合并差异，或停止覆盖 |
| 已编译的 PHP | Blade 的编译结果 | 通过 `view:cache` / `view:clear` 管理 |
| Finder 的注册路径与查找结果 | 运行中实例的视图选择 | 用新的代码和配置重启长时间运行的进程 |

`view:clear` 并不是一次性清除其他运行中进程所持有的 Finder 状态的命令。对于 Octane 等长时间运行的进程，请按照常规部署步骤重新加载。Finder 的 `flush()` 会清除查找结果，但不会注册新的覆盖目录。

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

除了包的测试之外，还要在使用方应用程序中确认以下组合。只渲染最新模板的测试无法验证对已发布旧版的用户的兼容性。

* 在未发布的状态下，渲染的是包中的视图。
* 只覆盖一个文件时，仅该文件优先，其他文件回退到包中。
* 在保留旧版已发布模板的状态下，也能使用新版传递的数据进行渲染。
* 普通的重新发布会保留自定义内容，未发布文件的添加也符合预期。
* 修改覆盖文件后 `view:cache` 成功，并且重新启动后显示的是修改后的内容。

## 相关页面

<Columns cols={2}>
  <Card title="视图" icon="eye" href="/zh-CN/views">
    了解视图的创建、数据传递和预编译的基础知识。
  </Card>

  <Card title="Blade 模板" icon="code" href="/zh-CN/blade">
    了解布局、include 和组件的用法。
  </Card>

  <Card title="版本兼容性管理" icon="code-branch" href="/zh-CN/advanced/package-versioning">
    将模板约定的变更与发布策略关联起来。
  </Card>

  <Card title="Octane" icon="bolt" href="/zh-CN/octane">
    了解长时间运行的应用程序的生命周期和重新加载。
  </Card>
</Columns>

## 参考的一手资料

* [Laravel 官方文档：包的视图](https://github.com/laravel/docs/blob/156fc7fde114548640e13c39aa79b991291c3f91/packages.md#views)
* [ServiceProvider：视图路径的注册](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Support/ServiceProvider.php)
* [FileViewFinder：查找顺序与查找结果的保存](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/View/FileViewFinder.php)
* [VendorPublishCommand：文件的发布条件](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Console/VendorPublishCommand.php)
* [ViewCacheCommand：视图路径的收集与编译](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Console/ViewCacheCommand.php)
* [ViewClearCommand：删除已编译文件](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Console/ViewClearCommand.php)
* [Compiler：修改时间的检查](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/View/Compilers/Compiler.php)


## Related topics

- [Laravel 包开发](/zh-CN/advanced/package-development.md)
- [进阶主题](/zh-CN/advanced/index.md)
- [视图](/zh-CN/views.md)
- [包迁移的发布与更新](/zh-CN/advanced/package-migrations.md)
- [路由](/zh-CN/routing.md)


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