昨天我們提到了 PSR-7,認知到 PHP 對於 HTTP 的 Request、Response 有一定程度的標準規範存在(還有一些 Utils,像是 UriInterface
)
在 PSR-7 漸漸成熟之後,PSR-15 便被提出,為的是強化 PSR-7 的實際應用層面。
對於 HTTP 協定,重點不外乎就是以下兩件事
在很多 Web Framework 中都有再多實現一個名為 Middleware 的層級,為的是在 HTTP Request 的生命週期插入一些功能。
Middleware 的概念如下圖所示
const Koa = require('koa');
const app = new Koa();
// Logger middleware
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});
// Response
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
package main
import (
"github.com/gin-gonic/gin"
"time"
)
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// Set example variable
c.Set("example", "12345")
// before request
c.Next()
// after request
latency := time.Since(t)
log.Print(latency)
// access the status we are sending
status := c.Writer.Status()
log.Println(status)
}
}
func main() {
r := gin.New()
r.Use(Logger())
r.GET("/test", func(c *gin.Context) {
example := c.MustGet("example").(string)
// it would print: "12345"
log.Println(example)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Psr7\Response;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
/`
* Example middleware closure
*
* @param ServerRequest $request PSR-7 request
* @param RequestHandler $handler PSR-15 request handler
*
* @return Response
*/
$beforeMiddleware = function (Request $request, RequestHandler $handler) {
$response = $handler->handle($request);
$existingContent = (string) $response->getBody();
$response = new Response();
$response->getBody()->write('BEFORE' . $existingContent);
return $response;
};
$afterMiddleware = function ($request, $handler) {
$response = $handler->handle($request);
$response->getBody()->write('AFTER');
return $response;
};
$app->add($beforeMiddleware);
$app->add($afterMiddleware);
$app->run();
註:以上範例皆來自於各 Framework 之官方文件
藉由「攔截」 HTTP Request 及 Response 的方式,可以在任意位置插入可控的邏輯,又因為 Middleware 應該只包含簡單的邏輯,故其具備較高的可測試性與可重用性。
通常一個 Middleware 會包含幾個要素
通常來說,當 next()
在後,表示請求時處理;當 next()
在前,表示回應時處理。
PSR-15 分為三個部份
RequestHandlerInterface
MiddlewareInterface
ResponseInterface
外加一個例外處理:Handler Exception
為了讓應用程式能夠使用 PSR-15 的功能,我們會需要一個 Dispatcher 負責協調 PSR-15 的各項功能。
在以下的範例中我會使用 relayphp/relay 作為我的 PSR-15 Dispatcher。
用法如下
// $request 為 ServerRequestInterface 的實體
return (new Relay)->handle($request);
// 位於 $middlewares 中的所有項目都是實現了 MiddlewareInterface 的實體
$middlewares = [];
return (new Relay($middlewares))->handle($request);
RequestHandler 的職責很簡單:接收一個 PSR-7 Request 並回應 PSR-7 Response,通常 RequestHandler 會作為 Middleware 的一部份,但也可以直接成為一個固定形式的功能。
這個介面需要實現一個 public function handle(ServerRequestInterface $request): ResponseInterface
class MarkdownHandler implements RequestHandler
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
// 確認是否存在 Markdown file
return file_exists($file = $request->getFile())
// 存在的話就編譯並作為 Response 回應
? $this->compileMarkdown($file)->getResponse()
// 不存在的話就直接 404 Not Found
: new Response(404);
}
}
// $request 已經轉化為 PSR-7 RequestInterface
return (new MarkdownHandler())->handle($request);
Middleware 的職責為:指派合適的 PSR-15 RequestHandler 給 PSR-7 Request,並且取得 PSR-7 Response。在此處,RequestHandler 通常是作為類似於 next()
的存在。
這個介面需要實現一個 public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
class AgeLimit implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
if ($request->getAge() < 18) {
return new ApplicationResponse(403);
}
// 類似於 next() 的存在,指派給下一個 Middleware
return $handler->handle($request);
}
}
class Logger implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
// 類似於 next() 存在,所以會先執行其它的 Middleware,最後再來執行這個 Logger
$response = $handler->handle($request);
// 借鑑於實現了 PSR-3 的 Logger Interface
Logger::debug("Time: {$response->getTime()}");
return $response;
}
}
$middlewares = [
AgeLimit::class,
Logger::class,
];
return (new Relay($middlewares))->handle($request);
對於 PSR-15,可以參考 middlewares/awesome-psr15-middlewares 這個 Repo。
裡面列舉了很多樣的 Dispatcher 或一些常見的功能,雖然數量還不多但對於自己開發仍有一定的參考價值。
今天應該是鐵人賽的最後一天,明天應該會以心得的方式帶過最後一天的內容。
這 29 天算是把一些 Modern PHPer 需要瞭解的東西做了一些整理,不過真正瞭解這些價值的人通常已經不再寫 PHP 了。Golang 或 Nodejs 的薪水要多得多
曾經聽過有人說:給我一個月,我能夠把一個不曾碰過程式的人讓他可以用 PHP 寫出產品。這大概也是 PHP 定位為「簡單易用」的原罪吧,無數的垃圾 Code 就此產生。
那想請問一下芥龍大,若我最近在專題上有開發restful api的需要,會建議我直接跳過php改採node.js開發嗎
我自己的php不是到很熟,所以還在猶豫要把php弄熟,還是改玩node.js甚至是golang QQ