> ## 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 的 Router 实现，讲解包的中间件别名注册、显式应用、避免覆盖分组以及缓存时的验证。

为包添加 HTTP 功能时，要把“让中间件可用”与“将其应用到使用方应用的请求上”区分开来。如果设计成仅安装包并不会改变所有路由的行为，就更容易管理引入时的影响和未来的兼容性。

本页以[包开发基础](/zh-CN/advanced/package-development)为前提，参考 Laravel 官方文档 `13.x` 以及 Framework `v13.36.0` 的实现。常规中间件的写法请参阅[中间件](/zh-CN/middleware)。

## 区分注册、应用与执行

`Router::aliasMiddleware()` 以别名为键、类名为值，将其注册到 Router 的数组中。它不会创建中间件实例，也不会将其应用到路由上。

```mermaid theme={null}
flowchart TD
    A["服务提供者的 boot"] --> B["通过 aliasMiddleware<br>注册名称与类"]
    C["路由定义"] --> D["通过 middleware<br>指定要应用的名称"]
    B --> E["确定请求的路由"]
    D --> E
    E --> F["展开别名与分组<br>应用排除与优先级"]
    F --> G["Pipeline 执行中间件"]
```

| 操作 | 作用 | 仅凭此操作不会发生的事 |
| - | - | - |
| `aliasMiddleware()` | 注册从名称到类的映射 | 应用到请求上 |
| 路由的 `middleware()` | 分配给目标路由 | 中间件类的具体实现 |
| `middlewareGroup()` | 设置命名分组的内容 | 添加到全局中间件 |
| 添加到 HTTP Kernel 的全局栈 | 以所有请求为对象 | 支持按路由排除 |

别名是公开 API。请避免使用 `auth`、`throttle` 等内置名称，以及容易与其他包冲突的通用名称，并加上包专属的前缀。如果对同一个键重新注册，`aliasMiddleware()` 会覆盖之前的映射。

## 在服务提供者中公开别名

下面的示例创建一个为响应添加固定标识头的中间件。这不是认证、授权或访问控制的示例。前提是通过 Composer 的 PSR-4 将 `Acme\Courier\` 映射到 `src/`，并通过[自动发现](/zh-CN/advanced/package-discovery)或手动方式注册服务提供者。

```php src/Http/Middleware/AddCourierHeader.php theme={null}
<?php

namespace Acme\Courier\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class AddCourierHeader
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        $response->headers->set('X-Acme-Courier', 'enabled');

        return $response;
    }
}
```

Router 的使用放在 `boot()` 中进行。`boot()` 的参数由服务容器注入，因此无需为了注册中间件而自动编辑使用方的 `bootstrap/app.php`。

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

namespace Acme\Courier;

use Acme\Courier\Http\Middleware\AddCourierHeader;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(Router $router): void
    {
        $router->aliasMiddleware('acme-courier.header', AddCourierHeader::class);
    }
}
```

请将此服务提供者作为常规服务提供者启动。如果把别名注册移到仅在解析其他服务时才执行的延迟服务提供者中，就无法保证在路由需要时已完成注册。

### 由使用方按路由应用

在使用方应用的 `routes/web.php` 中，只分配给需要的路由。

```php routes/web.php theme={null}
<?php

use Illuminate\Support\Facades\Route;

Route::get('/courier-status', function () {
    return response()->json(['available' => true]);
})->middleware('acme-courier.header');
```

也可以不提供别名，而是像 `->middleware(AddCourierHeader::class)` 这样直接指定类名。可根据是否需要为使用方提供简短名称来选择。

当包自身提供路由时，也要在路由文件中显式指定所需的中间件。`loadRoutesFrom()` 不会自动添加 `web` 或 `api`。注册方式与路由缓存的处理请参阅[包的路由注册与缓存](/zh-CN/advanced/package-routes)。

## 是否应用到整个应用由使用方选择

如果希望应用到使用方应用的所有 `web` 路由，由使用方在现有的 `withMiddleware()` 回调中添加以下配置。这并不是替换现有配置的示例。

```php theme={null}
use Acme\Courier\Http\Middleware\AddCourierHeader;
use Illuminate\Foundation\Configuration\Middleware;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->web(append: [
        AddCourierHeader::class,
    ]);
})
```

此配置直接使用类名，因此不依赖别名的注册顺序。适用范围是 `web` 分组，而不是全局栈。它不会自动应用到 `api` 路由，或未附加任何分组的路由。

<Warning>
  在安装包时就应用到整个 `web` 的设计，与由使用方显式应用的设计并不兼容。请不要通过自动更新，将影响认证、会话、响应头、外部通信等的处理扩展到所有路由。
</Warning>

## Router 的分组操作存在差异

当包提供自己的分组时，请使用专属名称。以下是放在服务提供者的 `boot(Router $router)` 中的示例。

```php theme={null}
$router->middlewareGroup('acme-courier', [
    AddCourierHeader::class,
]);
```

使用方可以通过 `->middleware('acme-courier')` 应用。不过，该分组只包含所指定的类，并不会继承 `web` 的会话或 CSRF 保护。

也有向现有分组添加的 API，但在 `v13.36.0` 的 Router 实现中存在以下差异。

| Router 的方法 | 分组存在时 | 分组不存在时 |
| - | - | - |
| `middlewareGroup($name, $items)` | 替换整个数组 | 用指定数组创建 |
| `pushMiddlewareToGroup($name, $item)` | 如果没有相同项则追加到末尾 | 先创建空数组再追加 |
| `prependMiddlewareToGroup($name, $item)` | 如果没有相同项则添加到开头 | 什么也不做 |

如果用 `middlewareGroup('web', [...])` 只设置包的类，就会替换掉现有 `web` 的内容。请不要与用于追加的方法混淆。此外，即使对不存在的 `web` 执行 `pushMiddlewareToGroup()`，也不会恢复标准的 `web` 分组。

追加方法的重复判定针对的是所注册的值。指向同一个类的别名与类名是不同的字符串，因此在注册表阶段会被视为不同的项。请统一写法，不要假设不同写法的同一个类会在注册时被去重。

### 不要仅靠注册顺序保证执行顺序

Router 会展开分配给路由的中间件别名与分组，处理排除后再按优先级排序。仅靠添加到开头或末尾，并不能保证与认证或路由模型绑定之间严格的先后关系。

此外，HTTP Kernel 的 `syncMiddlewareToRouter()` 会通过 `middlewareGroup()` 将 Kernel 持有的分组同步到 Router。如果 Kernel 一侧的配置之后被重新同步，直接添加到 Router 的分组内容可能会被替换。基本方针是：不要依靠包之间的启动顺序去争抢修改现有分组，而是在使用方应用一侧的 `withMiddleware()` 中集中配置。

对于需要“会话开始后”“认证后”等顺序的功能，请在安装步骤中写明该前提，并结合使用方应用的优先级设置进行确认。请避免由包整体替换应用的优先级列表。

## 缓存与验证的边界

请不要把别名注册放在 `routesAreCached()` 的条件内或路由文件中。存在缓存时，`loadRoutesFrom()` 不会加载路由文件，但为了在运行时解析缓存路由中保留的别名，仍需要进行注册。请在服务提供者的 `boot()` 中每次都进行注册。

如果更改了对路由的分配，请在使用方应用中重新生成路由缓存。

```bash theme={null}
php artisan route:cache
php artisan route:list --path=courier-status -vv
```

`-vv` 用于展开并确认分组的内容。不过，列表显示成功并不能保证中间件内的处理以及会话的使用。还需要进行 HTTP 请求测试。

下面是添加到定义了上述 `/courier-status` 的 Laravel 应用 Feature 测试中的方法。

```php theme={null}
public function test_courier_header_is_applied(): void
{
    $this->get('/courier-status')
        ->assertOk()
        ->assertHeader('X-Acme-Courier', 'enabled');
}
```

| 确认场景 | 预期结果 |
| - | - |
| 仅注册了别名的路由 | 不添加响应头 |
| 分配了别名的路由 | 添加响应头 |
| 直接分配类名的路由 | 行为相同 |
| 生成缓存后启动新的应用 | 同一路由行为相同 |
| 选择应用到整个 `web` | 应用到目标 `web` 路由，不扩展到范围之外 |
| 设置使用方应用自有的分组与优先级 | 与会话、认证、绑定的顺序满足要求 |

检查分组是否存在或别名数组的单元测试，与实际的 HTTP 执行是不同的验证。尤其是缓存验证，不能只查看同一进程中的 Router，而要在生成缓存后的新启动中进行。如果使用长驻 worker，请按照常规部署流程重新加载。

## 相关页面

<Columns cols={2}>
  <Card title="中间件" icon="layer-group" href="/zh-CN/middleware">
    了解创建、应用到路由、参数以及优先级的基础知识。
  </Card>

  <Card title="包的路由注册与缓存" icon="route" href="/zh-CN/advanced/package-routes">
    了解路由加载与生成缓存时配置之间的关系。
  </Card>
</Columns>

## 参考的一手资料

* [Laravel 官方文档：中间件](https://github.com/laravel/docs/blob/6bb0a3be3c623f7964c8277625a07b6df7f1de2e/middleware.md)
* [Laravel 官方文档：服务提供者的 boot 与依赖注入](https://github.com/laravel/docs/blob/6bb0a3be3c623f7964c8277625a07b6df7f1de2e/providers.md#the-boot-method)
* [Router：别名注册、分组操作以及中间件的展开与排序](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Routing/Router.php)
* [HTTP Kernel：syncMiddlewareToRouter](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Foundation/Http/Kernel.php)


## Related topics

- [包的路由注册与缓存](/zh-CN/advanced/package-routes.md)
- [包的事件监听器注册与缓存](/zh-CN/advanced/package-events.md)
- [包的 Artisan 命令注册与依赖解析](/zh-CN/advanced/package-commands.md)
- [延迟 Service Provider](/zh-CN/advanced/deferred-provider.md)
- [中间件](/zh-CN/middleware.md)


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