[PHP]路由套件 FastRoute

Kunyu
5 min readFeb 13, 2024
Photo by Marc-Olivier Jodoin on Unsplash

現代的 PHP 框架通常都包含了路由系統,這有助於組織和管理應用程式的路徑和路由。良好的路由系統設計不僅可以提高安全性,還可以改善代碼組織和可維護性,同時還有助於隱藏路徑和路由的細節,從而提升應用程式的安全性。

公司當中有些專案基於某些原因(可能是歷史包袱、開發策略…等等),不一定都會使用框架,替代方案是我們可以自己撰寫路由邏輯,若想學習寫出具備彈性的路由系統可參考 Dave Hollingworth 的教學,不過路由系統已經有成熟的套件,Josh Lockhart 建議我們應該善加利用其他開發者花了許多時間建立、測試、最佳化的原件,而不是重覆別人已經做過的事情,FastRoute 是由知名的 PHP 開發者 Nikita Popov 所創建。該路由器系統被設計為非常快速且輕量級,受到許多 PHP 開發者所喜愛。

專案環境配置

此專案使用 public/index.php 作為 Front Controller,亦即所有的 request 都會導到此檔案中做統一處理,此做法可以參考我之前的文章 [.htaccess]更改網站 root 位置,接著透過路由系統對應的檔案規劃存放於 src/ 目錄,當然你也可以隨意的設計你的軟體架構,但核心重點是需要把所有的 request 都集中到 Front Controller。

安裝套件

現代 PHP 使用套件管理工具 Composer 進行套件安裝,我們可以在 FastRoute Github README 一開頭就看到 To install with composer,實務上的舊專案可能沒有使用 composer,我會建議盡快評估使用,因為可以省去很多麻煩,例如你需要用一些方法來複製套件程式碼,然後擺到專案的某個資料夾載入,不同工程師在不同專案使用不同的做法,這都是無形的維護損耗,可以從我之前的文章 [PHP] Composer 套件管理工具 看出來 composer 入門非常簡單。

使用路由配置

此篇以官方Github提供的簡單的範例做一點點延伸,我配置三個路由,分別是首頁(index)、計畫(project)、使用者(users),我在底下的程式碼中有註解的地方是需要寫入的資訊,這樣就可以實現一個最簡單帶有路由系統的 PHP 專案了,後續讀者可再對程式碼結構進行優化,例如參考 Laravel 獨立routes 設定檔、使用 Controller 以及遵循 SOLID 原則等等。

<?php

require __DIR__ . '/../vendor/autoload.php';

$dispatcher = FastRoute\simpleDispatcher(function (FastRoute\RouteCollector $r) {
// (1) 這邊要寫入想匹配的路由
$r->addRoute('GET', '/', 'index');
$r->addRoute('GET', '/project', 'project');
$r->addRoute('GET', '/users/{id:\d+}', 'users');
});

$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];

if (false !== $pos = strpos($uri, '?')) {
$uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);

$routeInfo = $dispatcher->dispatch($httpMethod, $uri);

switch ($routeInfo[0]) {
case FastRoute\Dispatcher::NOT_FOUND:
echo '404';
break;
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
$allowedMethods = $routeInfo[1];
echo '405';
break;
case FastRoute\Dispatcher::FOUND:
$handler = $routeInfo[1];
$vars = $routeInfo[2];
// (2) 這邊要寫入路由匹配之後做什麼事情(本例為載入PHP檔案)
printMatchFile($handler, $vars);
break;
}

// (3) 自定義載入函數
function printMatchFile($handler, $vars)
{
switch ($handler) {
case 'index':
require __DIR__ . '/../src/index.php';
break;
case 'project':
require __DIR__ . '/../src/project.php';
break;
case 'users':
$userId = $vars['id'];
require __DIR__ . '/../src/users.php';
break;
}
}

--

--