現代 PHP 框架(Laravel、Symfony、ThinkPHP、Zend Framework)均採用「單一入口 + 路由分發」架構。本文實現一個極簡的兩階段路由器:
REQUEST_URI / QUERY_STRING 提取 controller、action、params代碼參考 Zend Framework 1 風格,適合學習原理或輕量級項目直接使用。
url_router()<?php
/**
* 解析當前請求的路由信息
* 支持三種 URL 形式:
* /path/to/index.php/controller/action/param/value
* /path/to/index.php?/controller/action/param/value
* /path/to/site/?/controller/action/param/value
* @return array ['controller'=>..., 'action'=>..., 'params'=>[...]]
*/
function url_router(): array
{
$path = strip_tags($_SERVER['REQUEST_URI']); // 防 XSS
$pos = strpos($path, '.php');
if ($pos !== false) {
// 情況 1 & 2:有 .php,取其後部分
$path = substr($path, $pos + 4);
} else {
// 情況 3:無 .php(如 PATH_INFO 模式或重寫後)
if (empty($_SERVER['QUERY_STRING'])) {
$qPos = strpos($path, '?');
if ($qPos !== false) {
$path = substr($path, $qPos + 1);
} else {
$path = '';
}
} else {
$path = $_SERVER['QUERY_STRING'];
}
}
// 統一去除首部 /
if ($path !== '' && $path[0] === '/') {
$path = substr($path, 1);
}
// 默認路由
$router = [
'controller' => 'index',
'action' => 'index',
'params' => [],
];
if ($path !== '') {
$parts = explode('/', $path);
$router['controller'] = $parts[0] ?? 'index';
$router['action'] = $parts[1] ?? 'index';
// 其餘成對作為參數
$params = [];
for ($i = 2; $i < count($parts); $i += 2) {
$key = $parts[$i] ?? '';
$value = $parts[$i + 1] ?? '';
if ($key !== '') $params[$key] = $value;
}
$router['params'] = $params;
}
return $router;
}
?>
url_dispatch()<?php
/**
* 根據路由信息分發到控制器
* @param array $router url_router() 返回的陣列
* @param string $appPath 控制器目錄相對項目根目錄路徑
* @return bool
*/
function url_dispatch(array $router, string $appPath = '/app/controllers/'): bool
{
// 1. 載入基礎控制器類(假設項目有統一基類)
require_once SERVER_PATH . '/libs/controller.class.php';
// 2. 構建控制器類名:首字母大寫 + Controller 後綴
$className = ucfirst($router['controller']) . 'Controller';
$filePath = SERVER_PATH . $appPath . $className . '.class.php';
if (!file_exists($filePath)) {
die('缺少必要的控制器類:' . $className);
}
require_once $filePath;
// 3. 實例化並注入參數
$controller = new $className();
if (method_exists($controller, '_setParam')) {
$controller->_setParam($router['params']);
}
// 4. 調用動作方法:action 名 + Action 後綴
$actionMethod = $router['action'] . 'Action';
if (!method_exists($controller, $actionMethod)) {
die('動作方法不存在:' . $actionMethod);
}
$controller->$actionMethod();
return true;
}
?>
index.php 整合<?php
define('SERVER_PATH', __DIR__); // 項目根目錄
require_once 'router.php'; // 包含上述兩個函數
// 1. 解析路由
$router = url_router();
// 2. 分發執行
url_dispatch($router);
// 3. 程序結束(控制器內部負責輸出)
?>
controller.class.php<?php
abstract class Controller
{
protected $params = [];
public function _setParam(array $params)
{
$this->params = $params;
}
protected function param(string $key, $default = null)
{
return $this->params[$key] ?? $default;
}
protected function view(string $template, array $data = [])
{
extract($data);
ob_start();
include VIEW_PATH . '/' . $template . '.php';
return ob_get_clean();
}
}
?>
app/controllers/IndexController.class.php<?php
class IndexController extends Controller
{
public function indexAction()
{
$name = $this->param('name', 'Guest');
echo $this->view('welcome', ['name' => $name]);
}
public function helloAction()
{
echo "Hello, " . $this->param('name', 'World');
}
}
?>
| 訪問 URL | controller | action | params |
|---|---|---|---|
/index.php/index/index | index | index | [] |
/index.php/index/hello/name/John | index | hello | ['name' => 'John'] |
/?/index/hello/name/John | index | hello | ['name' => 'John'] |
/index.php?/index/hello/name/John | index | hello | ['name' => 'John'] |
/admin/user/list → controller=admin_user 或命名空間 Admin\UserControllerFastRoute 或自寫正則匹配表,支持 GET /user/{id:\d+} 參數綁定try/catch 捕獲 HttpException(404, 500) 渲染錯誤頁僅用兩個函數(url_router + url_dispatch)即可構建出一個功能完整的 MVC 分發核心。理解「請求解析 → 路由匹配 → 控制器實例化 → 動作調用」這一流程,是掌握所有現代 PHP 框架內部機制的鑰匙。輕量級項目可直接使用本代碼;中大型項目建議直接接入成熟路由組件(nikic/fast-route、league/route、框架內置),專注業務邏輯開發。