您可以在 Slim 应用程序之前和之后运行代码,以适当地操作请求和响应对象。这种操作称为中间件。为什么要这样做?也许您希望保护您的应用程序免受跨站点请求伪造的影响。也许您希望在您的应用程序运行之前对请求进行身份验证。中间件非常适合这些场景。
中间件是 Web 应用程序中位于客户端请求和服务器响应之间的层。当 HTTP 请求和响应通过应用程序管道时,中间件会拦截、处理并可能更改这些请求和响应。
中间件可以处理各种任务,例如身份验证、授权、日志记录、请求修改、响应转换、错误处理等。
每个中间件执行其功能,然后将控制权传递给链中的下一个中间件,从而在 Web 应用程序中实现一种模块化且可重用的方法以处理横切关注点。
不同的框架对中间件的使用方式不同。Slim 将中间件添加为围绕核心应用程序的同心层。每个新中间件层都会包围任何现有的中间件层。随着添加额外的中间件层,同心结构会向外扩展。
添加的最后一个中间件层是最先执行的层。
您运行 Slim 应用程序时,请求对象由外向内遍历中间件结构。它们先进入最外面的中间件,然后进入下一个最外面的中间件(依此类推),最后才能到达 Slim 应用程序本身。Slim 应用程序调度相应的路由之后,响应结果对象将退出 Slim 应用程序,并由内向外遍历中间件结构。最终,最终的响应对象将退出最外面的中间件,序列化为原始的 HTTP 响应并返回到 HTTP 客户端。下面是一个阐明中间件流程的图表
中间件是可以接受两个参数的回调函数:一个 请求
对象和一个 RequestHandler
对象。每种中间件必须返回 Psr\Http\Message\ResponseInterface
的一个实例。
此示例中间件是一个闭包。
<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
$beforeMiddleware = function (Request $request, RequestHandler $handler) use ($app) {
// Example: Check for a specific header before proceeding
$auth = $request->getHeaderLine('Authorization');
if (!$auth) {
// Short-circuit and return a response immediately
$response = $app->getResponseFactory()->createResponse();
$response->getBody()->write('Unauthorized');
return $response->withStatus(401);
}
// Proceed with the next middleware
return $handler->handle($request);
};
$afterMiddleware = function (Request $request, RequestHandler $handler) {
// Proceed with the next middleware
$response = $handler->handle($request);
// Modify the response after the application has processed the request
$response = $response->withHeader('X-Added-Header', 'some-value');
return $response;
};
$app->add($afterMiddleware);
$app->add($beforeMiddleware);
// ...
$app->run();
此示例中间件是一个可调用类,实现了魔法 __invoke()
方法。
<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Message\ResponseInterface as Response;
class ExampleBeforeMiddleware
{
public function __invoke(Request $request, RequestHandler $handler): Response
{
// Handle the incoming request
// ...
// Invoke the next middleware and return response
return $handler->handle($request);
}
}
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class ExampleAfterMiddleware
{
public function __invoke(Request $request, RequestHandler $handler): Response
{
// Invoke the next middleware and get response
$response = $handler->handle($request);
// Handle the outgoing response
// ...
return $response;
}
}
PSR-15 是一种标准,用于为基于 HTTP 的服务器错误处理程序和中间件组件定义通用接口。
Slim 为 PSR-15 中间件提供内置支持。
主要接口
Psr\Http\Server\MiddlewareInterface
:此接口定义了中间件必须实现的process 方法。Psr\Http\Server\RequestHandlerInterface
:一个 HTTP 请求处理程序,可以处理 HTTP 请求以生成 HTTP 响应。要创建 PSR-15 中间件类,您需要实现 MiddlewareInterface
。
下面是一个简单的 PSR-15 中间件示例。
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class ExampleMiddleware implements MiddlewareInterface
{
public function process(Request $request, RequestHandler $handler): Response
{
// Optional: Handle the incoming request
// ...
// Invoke the next middleware and get response
$response = $handler->handle($request);
// Optional: Handle the outgoing response
// ...
return $response;
}
}
可以对传入请求进行身份验证、授权、记录、验证或修改。
可以对传出响应进行记录、转换、压缩或添加其他头域。
要创建新的响应,请使用 Psr\Http\Message\ResponseFactoryInterface
,它提供了一种 createResponse()
方法来创建一个新的响应对象。
下面是一个创建新响应的 PSR-15 中间件类示例
<?php
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ExampleMiddleware implements MiddlewareInterface
{
private ResponseFactoryInterface $responseFactory;
public function __construct(ResponseFactoryInterface $responseFactory)
{
$this->responseFactory = $responseFactory;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// Check some condition to determine if a new response should be created
if (true) {
// Create a new response using the response factory
$response = $this->responseFactory->createResponse();
$response->getBody()->write('New response created by middleware');
return $response;
}
// Proceed with the next middleware
return $handler->handle($request);
}
}
响应默认情况下使用 200
OK 状态代码创建。要更改 HTTP 状态代码,您可以将所需状态代码作为参数传递给 createResponse
方法。
$response = $this->responseFactory->createResponse(201);
请注意,响应工厂是一个必须注入到中间件中的依赖项。请确保 Slim
DI 容器(例如 PHP-DI)已正确配置,以提供 Psr\Http\Message\ResponseFactoryInterface
的一个实例。
示例:使用 slim\psr7
包执行 PHP-DI 定义
use Psr\Container\ContainerInterface;
use Slim\Psr7\Factory\ResponseFactory;
// ...
return [
// ...
ResponseFactoryInterface::class => function (ContainerInterface $container) {
return $container->get(ResponseFactory::class);
},
];
示例:使用 nyholm/psr7
包执行 PHP-DI 定义
use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Container\ContainerInterface;
// ...
return [
// ...
ResponseFactoryInterface::class => function (ContainerInterface $container) {
return $container->get(Psr17Factory::class);
},
];
要使用中间件,你需要在 Slim 的 $app、路由或路由组上注册每个中间件。
// Add middleware to the App
$app->add(new ExampleMiddleware());
// Add middleware to the App using dependency injection
$app->add(ExampleMiddleware::class);
// Add middleware to a route
$app->get('/', function () { ... })->add(new ExampleMiddleware());
// Add middleware to a route group
$app->group('/', function () { ... })->add(new ExampleMiddleware());
Slim 按后进先出 (LIFO) 顺序处理中间件。这意味着最后添加的中间件将首先执行。如果你添加了多个中间件组件,它们将按照添加顺序的反向执行。
$app->add(new MiddlewareOne());
$app->add(new MiddlewareTwo());
$app->add(new MiddlewareThree());
在这种情况下,MiddlewareThree
将首先执行,然后是 MiddlewareTwo
,最后是 MiddlewareOne
。
只有在路由与当前 HTTP 请求方法和 URI 匹配的情况下,才会调用路由中间件。在调用 Slim 应用程序的任何路由方法(例如,get() 或 post())后,立即指定路由中间件。每个路由方法都会返回一个 \Slim\Route 实例,此类提供与 Slim 应用程序实例相同的中间件界面。通过 Route 实例的 add() 方法将中间件添加到路由。此示例添加了上述闭包中间件示例
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
$middleware = function (Request $request, RequestHandler $handler) {
$response = $handler->handle($request);
$response->getBody()->write('World');
return $response;
};
$app->get('/', function (Request $request, Response $response) {
$response->getBody()->write('Hello ');
return $response;
})->add($middleware);
$app->run();
这将输出此 HTTP 响应正文
Hello World
除了整体应用程序和标准路由能够接受中间件之外,group() 多路由定义功能还允许使用单个内部路由。只有在路由与组中定义的一个 HTTP 请求方法和 URI 匹配的情况下,才会调用路由组中间件。要在回调中添加中间件,并通过在 group() 方法后链接 add() 来设置整个组的中间件。
示例应用程序,在 URL 处理程序组上使用回调中间件。
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Routing\RouteCollectorProxy;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
$app->get('/', function (Request $request, Response $response) {
$response->getBody()->write('Hello World');
return $response;
});
$app->group('/utils', function (RouteCollectorProxy $group) {
$group->get('/date', function (Request $request, Response $response) {
$response->getBody()->write(date('Y-m-d H:i:s'));
return $response;
});
$group->get('/time', function (Request $request, Response $response) {
$response->getBody()->write((string)time());
return $response;
});
})->add(function (Request $request, RequestHandler $handler) use ($app) {
$response = $handler->handle($request);
$dateOrTime = (string) $response->getBody();
$response = $app->getResponseFactory()->createResponse();
$response->getBody()->write('It is now ' . $dateOrTime . '. Enjoy!');
return $response;
});
$app->run();
调用 /utils/date 方法时,将输出类似于以下内容的字符串。
It is now 2015-07-06 03:11:01. Enjoy!
访问 /utils/time 将输出类似于以下内容的字符串。
It is now 1436148762. Enjoy!
但访问 / (域根) 时,由于未分配任何中间件,因此预期会生成以下输出。
Hello World
从中间件传递属性的最简单方法是使用请求的属性。
在中间件中设置变量
$request = $request->withAttribute('foo', 'bar');
在路由回调中获取变量
$foo = $request->getAttribute('foo');
你可能会找到一个已编写的 PSR-15 中间件类来满足你的需求。以下是几个非官方的搜索列表。