中间件
如果我们查看传入的 HTTP 请求,每个请求都有 Laravel 的 index.php
文件处理并通过一系列处理后返回给浏览器。其中就包括一系列的中间件,每个中间件都会在请求到达应用程序核心之前对其进行处理。还有一些中间件是在核心响应后,且该响应还未返回浏览器时进行修改。
「译者注」怎么理解这句话呢?根据拦截时间节点,中间件就分为两种。
- 请求到达 Controller 之前:例如权限验证、CSRF 验证中间件等。
- 响应返回浏览器之前:例如 设置 Cookie 数据(AddQueuedCookiesToResponse)。
这就是为什么中间件适合做权限验证、验证 Token 或其他必要检查的原因,Laravel 还使用中间件把请求的空字符串转换为 null、加密 cookie 等。
创建中间件
中间件基本分为两种类型:
- 应用处理请求 之前 进行操作。
- 应用请求 之后 进行操作。
「译者注」你可以在 Laravel 文档的 中间件 进行查看。
在讨论两种类的中间件之前,首先在src/Http
目录创建一个Middleware
文件夹,用于存放所有的中间件。
前置中间件
前置中间件将在应用处理请求 之前 执行一些任务。通常,前置中间件采用以下形式:
<?php
namespace App\Http\Middleware;
use Closure;
class BeforeMiddleware
{
public function handle($request, Closure $next)
{
// Perform action
return $next($request);
}
}
前置中间件的说明,让我们添加一个中间件,这个中间件的作用是如果请求中有 title 参数时,我们将对其首字母进行大写转换。(仅仅是个示例,如有雷同,纯属巧合)
创建一个名为 CapitalizeTitle.php
的文件,该文件提供了一个 handle()
方法,该方法同时接收当前请求和 $next
操作:
// 'src/Http/Middleware/CapitalizeTitle.php'
<?php
namespace JohnDoe\BlogPackage\Http\Middleware;
use Closure;
class CapitalizeTitle
{
public function handle($request, Closure $next)
{
if ($request->has('title')) {
$request->merge([
'title' => ucfirst($request->title)
]);
}
return $next($request);
}
}
测试前置中间件
虽然我们尚未在服务提供者中注册中间件,并且目前也没有在扩展包中使用它,但是我们依然要保证 handle()
方法正确执行。
在 tests/Unit
文件夹创建一个新的 CapitalizeTitleMiddlewareTest.php
单元测试文件。测试文件主要确认在中间件运行handle()
方法后,Request()
中的 title 参数值的首字母是否变大写了:
// 'tests/Unit/CapitalizeMiddlewareTest.php'
<?php
namespace JohnDoe\BlogPackage\Tests\Unit;
use Illuminate\Http\Request;
use JohnDoe\BlogPackage\Http\Middleware\CapitalizeTitle;
use JohnDoe\BlogPackage\Tests\TestCase;
class CapitalizeTitleMiddlewareTest extends TestCase
{
/** @test */
function it_capitalizes_the_request_title()
{
// 创建一个请求
$request = new Request();
// 设置 title 参数,值为全小写
$request->merge(['title' => 'some title']);
// 将请你求传递给中间件后,
// 应该将标题的首字母变大写
(new CapitalizeTitle())->handle($request, function ($request) {
$this->assertEquals('Some title', $request->title);
});
}
}
后置中间件
后置中间件是对所有数据处理完成后,在返回响应前对其进行修改。通常,他采用下面的形式:
<?php
namespace App\Http\Middleware;
use Closure;
class AfterMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
// Perform action
return $response;
}
}
测试后置中间件
与前置中间件类似,我们可以在请求的 Response
上执行的中间件进行测试,并在其传递到下一层之前对其进行修改。例如,我们有一个 InjectHelloWorld
中间件,它的作用是在每次响应中注入 'Hello World' 字符串,通过下面的测试代码进行测试:
// 'tests/Unit/InjectHelloWorldMiddlewareTest.php'
<?php
namespace JohnDoe\BlogPackage\Tests\Unit;
use Illuminate\Http\Request;
use JohnDoe\BlogPackage\Http\Middleware\InjectHelloWorld;
use JohnDoe\BlogPackage\Tests\TestCase;
class InjectHelloWorldMiddlewareTest extends TestCase
{
/** @test */
function it_checks_for_a_hello_word_in_response()
{
// 创建一个请求
$request = new Request();
// 将请求传入中间件,它会把 'Hello World' 插入到响应中
$response = (new InjectHelloWorld())->handle($request, function ($request) { });
$this->assertStringContainsString('Hello World', $response);
}
}
现在我们看到 handle()
方法可以正确执行,下面我们看一下注册中间件的两个选项:全局 vs 指定路由。
全局中间件
顾名思义,全局中间件的作用域是全局应用的,每个请求都将经过这个中间件处理。
如果我们希望检查标题在全局应用,则需要将 CapitalizeTitle
中间件添加到 Http\Kernel
文件中你那个,千万不要加到 Console Kernel 中:
// 'BlogPackageServiceProvider.php'
use Illuminate\Contracts\Http\Kernel;
use JohnDoe\BlogPackage\Http\Middleware\CapitalizeTitle;
public function boot()
{
// other things ...
$kernel = $this->app->make(Kernel::class);
$kernel->pushMiddleware(CapitalizeTitle::class);
}
这样会把 CapitalizeTitle
中间件加到应用程序的全局注册中间件数组中。
路由中间件
看到这里,你可能会说,在项目中不可能每个请求中都有 title 参数啊,设置为全局中间件会有问题。是的,这个中间件应该只针对创建、更新帖子的相关请求。
但是,目前我们示例的中间件是将所有具有 title 参数的请求都修改了,这不是我们想要的结果,解决方案就是使用路由中间件来解决。
我们需要在服务提供者的 boot()
方法中,先将 Router
类解析出来,在通过 aliasMiddleware
方法注册中间件,并注册别名为 capitalize
,后续在控制器中使用。
// 'BlogPackageServiceProvider.php'
use Illuminate\Routing\Router;
use JohnDoe\BlogPackage\Http\Middleware\CapitalizeTitle;
public function boot()
{
// other things ...
$router = $this->app->make(Router::class);
$router->aliasMiddleware('capitalize', CapitalizeTitle::class);
}
在控制器的构造方法中应用 capitalize
中间件:
// 'src/Http/Controllers/PostController.php'
class PostController extends Controller
{
public function __construct()
{
$this->middleware('capitalize');
}
// other methods... (will use this middleware)
}
测试
无论是全局中间件还是路由中间件,我们都可以请求时测试中间件是否适用。
添加新测试到 CreatePostTest
文件中,在该测试中,我们假设帖子保存时,需要将标题的首字母大写。
// 'tests/Feature/CreatePostTest.php'
/** @test */
function creating_a_post_will_capitalize_the_title()
{
$author = factory(User::class)->create();
$this->actingAs($author)->post(route('posts.store'), [
'title' => 'some title that was not capitalized',
'body' => 'A valid body',
]);
$post = Post::first();
// 'New: ' was added by our event listener
$this->assertEquals('New: Some title that was not capitalized', $post->title);
}
随着测试变为绿色通过状态,我们完成了把中间件添加到扩展包的整个过程。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: