首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

ThinkPHP 路由規則深度解析:正則路由、偽靜態、參數綁定完整實作

     編輯:PHP 相關知識

前言

ThinkPHP 3.x 的路由系統支援 規則路由(規則字串解析) 與 正則路由(PCRE 直接匹配) 兩種模式,並整合 Apache mod_rewrite 實現偽靜態。本文完整還原核心解析函式 parseUrlRule()、parseUrlRuleRegx(),並解析參數提取、多規則合併、分組/模組/操作識別等關鍵機制。

路由定義格式

$route = [
    // 規則路由:動態參數用 :name,:name^ 排除值,:named 僅匹配數字
    'news/:action/:year/:month/:day' => 'news/read?year=:2&month=:3&day=:4',
    
    // 陣列格式:[目標URL, 額外合併參數]
    'news/:action^delete|update|insert/:year/:month/:day' => [
        'news/read?extra=:2&status=1',
        'year=:2&month=:3&day=:4'
    ],
    
    // 正則路由:鍵為 PCRE 模式
    '/news/read/(\d+)/(\d+)/(\d+)/' => 'news/read?year=:1&month=:2&day=:3',
];

核心解析流程(簡化版)

<?php
function parseUrlRule(string $rule, $route, string $pathinfo, string $ext = 'html'): bool {
    // 1. 移除副檔名
    $pathinfo = str_replace('.' . $ext, '', $pathinfo);
    
    // 2. 切割規則與請求路徑
    $ruleParts = explode('/', $rule);
    $pathParts = explode('/', $pathinfo);
    
    // 3. 逐段匹配
    $matches = [];
    foreach ($ruleParts as $i => $part) {
        if (strpos($part, ':') === 0) { // 動態參數
            $key = substr($part, 1);
            $isNumeric = substr($key, -1) === 'd'; // :year_d
            $exclude = false;
            if (strpos($key, '^')) { // :action^delete|update
                [$key, $excludeList] = explode('^', $key);
                $exclude = explode('|', $excludeList);
            }
            $val = $pathParts[$i] ?? '';
            if ($isNumeric && !ctype_digit($val)) return false;
            if ($exclude && in_array($val, $exclude)) return false;
            $matches[$key] = $val;
        } else { // 靜態段落,不分大小寫比對
            if (strcasecmp($part, $pathParts[$i] ?? '') !== 0) return false;
        }
    }
    
    // 4. 組裝目標 URL
    $targetUrl = is_array($route) ? $route[0] : $route;
    // 替換 :1, :2... 為對應匹配值
    $targetUrl = preg_replace_callback('/:(\d+)/', function($m) use ($matches, $ruleParts) {
        // 依規則順序取值
        $keys = array_keys($matches);
        return $matches[$keys[$m[1]-1]] ?? '';
    }, $targetUrl);
    
    // 5. 解析目標 URL 為 module/action + query 參數
    parseTargetUrl($targetUrl, $matches, $pathParts, $route);
    
    return true;
}

function parseTargetUrl(string $url, array $matches, array $remainingPath, $route) {
    // 解析 module/action
    if (strpos($url, '?') !== false) {
        [$path, $query] = explode('?', $url, 2);
        parse_str($query, $queryParams);
    } else {
        $path = $url;
        $queryParams = [];
    }
    $pathParts = explode('/', $path);
    $_GET['actionName'] = array_pop($pathParts);
    $_GET['moduleName'] = array_pop($pathParts);
    if ($pathParts) $_GET['groupName'] = array_pop($pathParts);
    
    // 合併路由規則中的參數
    $_GET = array_merge($_GET, $queryParams, $matches);
    
    // 合併剩餘路徑參數(如 extraparam/test)
    preg_replace_callback('/(\w+)\/([^\/]+)/', function($m) {
        $_GET[$m[1]] = $m[2];
    }, implode('/', $remainingPath));
    
    // 陣列路由的第二個元素
    if (is_array($route) && isset($route[1])) {
        parse_str($route[1], $extra);
        $_GET = array_merge($_GET, $extra);
    }
    
    $_GET['finalUrl'] = $url;
    $_REQUEST = array_merge($_REQUEST, $_GET);
}

正則路由解析 parseUrlRuleRegx()


function parseUrlRuleRegx(string $regex, $route, string $pathinfo, string $ext = 'html'): bool {
    $pathinfo = str_replace('.' . $ext, '', $pathinfo);
    $targetUrl = is_array($route) ? $route[0] : $route;
    
    if (!preg_match($regex, $pathinfo, $matches)) return false;
    
    // $matches[0] 為完整匹配,[1] [2]... 為捕獲組
    $targetUrl = preg_replace_callback('/:(\d+)/', function($m) use ($matches) {
        return $matches[$m[1]] ?? '';
    }, $targetUrl);
    
    // 移除已匹配部分,剩餘路徑轉參數
    $remaining = str_replace($matches[0], '', $pathinfo);
    parseTargetUrl($targetUrl, [], explode('/', trim($remaining, '/')), $route);
    
    return true;
}

Apache 偽靜態設定


# .htaccess 或 httpd.conf
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)$ index.php/$1 [L,QSA]

# 將 /news/read/2012/2/21 轉為 /index.php/news/read/2012/2/21
# PHP 端 $_SERVER['PATH_INFO'] 即為 news/read/2012/2/21

實戰測試案例

請求 URL匹配規則產生 $_GET
/news/read/2012/2/21/extraparam/test.htmlnews/:action/:year/:month/:day['actionName'=>'read','moduleName'=>'news','year'=>'2012','month'=>'2','day'=>'21','extraparam'=>'test']
/news/delete/2012/2/21.htmlnews/:action^delete|update/:year/:month/:day不匹配(action 被排除)
/news/read/2012/2/21.html/news/read/(\d+)/(\d+)/(\d+)/['actionName'=>'read','moduleName'=>'news','year'=>'2012','month'=>'2','day'=>'21']

遷移至 ThinkPHP 5/6/8 路由寫法對照


// TP3 規則路由
'news/:action/:year/:month/:day' => 'news/read?year=:2&month=:3&day=:4'

// TP5/6/8 路由定義(route.php)
Route::rule('news/:action/:year/:month/:day', 'news/read', 'GET', [], [
    'year'   => '\d{4}',
    'month'  => '\d{1,2}',
    'day'    => '\d{1,2}',
]);

// TP5/6/8 正則路由
Route::rule('/news/read/(\d+)/(\d+)/(\d+)/', 'news/read?year=:1&month=:2&day=:3');

結語

ThinkPHP 3.x 的路由解析器雖屬手工打造,但其「規則字串轉正則、動態參數提取、多來源參數合併(路由規則+剩餘路徑+陣列額外參數)」的設計,完整支援了 MVC 分派所需的所有資訊。理解此機制,不僅利於維護舊專案,更能幫助你在設計自有框架或中介軟體時,建立清晰的「請求路徑 → 參數陣列 → 控制器動作」轉換管線。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved