您可以在 Slim 應用程式之前和之後執行程式碼,以自行設定請求和回應物件。這稱為中間件。為什麼您想這麼做?可能是您想保護您的 app 免於跨網站請求偽造。也可能是您想在 app 執行之前驗證請求。在這些情況下,中間件就是完美的選擇。
中間件是一個位於網頁應用程式中,於客戶端請求和伺服器回應之間的層級。當它們穿過應用程式管線時,中間件會攔截、處理,並可能更動 HTTP 請求和回應。
中間件可以處理驗證、授權、記錄、請求修改、回應轉換、錯誤處理等多項任務。
每個中間件執行其功能,然後將控制權傳遞給鏈中的下一個中間件,這使得在網頁應用程式中處理橫切關注項時,能夠採模組化且可重複使用的做法。
不同的架構會以不同的方式使用中間件。Slim 會在核心應用程式外圍新增中間件作為同心圓形狀的層級。每個新的中間件層級會包圍現有的任何中間件層級。同心圓形的結構會隨著加入更多中間件層級而向外擴張。
最後新增的中介層會先執行。
您執行 Slim 應用程式時,Request 物件會由外往內穿透中介層結構。它們會先進入最外層的中介層,然後進入次外層的中介層(依此類推),直到最後抵達 Slim 應用程式本身。在 Slim 應用程式指派適當的路由之後,結果的 Response 物件會離開 Slim 應用程式並由內往外穿透中介層結構。最後,一個最終的 Response 物件會離開最外層的中介層,序列號化成為一個原始 HTTP 回應,並傳回 HTTP 客戶端。以下是說明中介層程序流程的圖表
中介層是一個可呼叫的,接受兩個參數的物件:一個 Request
物件和一個 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 會遵循 Last In, First Out (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() 方法新增中介軟體到 Route。這個範例新增了上述的 Closure 中介軟體範例
<?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 中介軟體類別。以下列出幾個非官方清單供您搜尋。