路由模塊

從零開始編寫一個(gè)PHP框架 系列的《路由模塊》

項(xiàng)目地址:terse

前言

路由類左腔,是用戶請求的門神铃剔,它過濾了不符合當(dāng)前規(guī)則的鏈接。同時(shí)析恢,也美化了我們的 URL。一個(gè)好的路由類秧饮,會(huì)讓我們的程序更加靈活映挂。

需求分析

  • 可以指定請求類型泽篮,如果是 any 說明不限制類型
  • 可以使用正則
  • 可以指定控制器、方法及其前后綴
  • 可以指定命名空間

文件結(jié)構(gòu)

└── Mvc
    ├── Router
    │   └── Route.php    [路由定義解析類]
    └── Router.php       [路由配置類]

路由配置類

指定請求類型的路由

我們想要實(shí)現(xiàn)一個(gè)類似于下面情況的路由:

<?php
$router = new Router();
$router->get('/', ['controller' => 'Home', 'action' => 'index']);
$router->post('/analysis.do', ['controller' => 'Home', 'action' => 'analysis']);

$router->get('/edit.do', ['controller' => 'Article', 'action' => 'edit']);
$router->post('/edit.do', ['controller' => 'Article', 'action' => 'doEdit']);

所以從上面可以看出柑船,我們可以給某一個(gè)路由添加不同的請求類型帽撑。

既然說到請求類型,那么就得用到上一篇?jiǎng)倢懞玫恼埱竽K了鞍时。

當(dāng)然亏拉,如果我們需要某一個(gè)路由無論什么請求方式都可以的話,可以考慮用 any逆巍。

<?php
use Request;
class Router
{
    const METHOD_ANY = 'any';

    /**
     * 路由信息
     * 
     * @var array
     */
    protected $_routes  [];

    /**
     * 設(shè)置請求方式為GET的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function get($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_GET);
        return $this;
    }

    /**
     * 設(shè)置請求方式為POST的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function post($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_POST);
        return $this;
    }

    /**
     * 設(shè)置請求方式為PUT的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function put($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_PUT);
        return $this;
    }

    /**
     * 設(shè)置請求方式為DELETE的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function delete($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_DELETE);
        return $this;
    }

    /**
     * 設(shè)置請求方式為HEAD的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function head($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_HEAD);
        return $this;
    }

    /**
     * 設(shè)置請求方式為OPTIONS的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function options($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_OPTIONS);
        return $this;
    }

    /**
     * 設(shè)置請求方式為PATCH的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function patch($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_PATCH);
        return $this;
    }

    /**
     * 設(shè)置任意請求方式的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function any($uri, array $rules)
    {
        $this->add($uri, $rules, self::METHOD_ANY);
        return false;
    }
}

在上述代碼中及塘,我們看到,每一個(gè)方法都調(diào)用了 add 方法锐极,這個(gè)方法主要是用來統(tǒng)一定義路由的笙僚,如果有什么需要修改或者添加的,可以在這里擴(kuò)展灵再。

關(guān)于 add 方法里用到的 Route 類肋层,會(huì)在后續(xù)說明。

<?php
use Request;
class Router
{
    ...

    /**
     * 添加一種規(guī)則
     * 
     * @param string $uri
     * @param array  $rules
     * @param string $method
     */
    protected function add($uri, array $rules, $method)
    {
        $this->_routes[$method][] = new Route($uri, $rules);
    }
}

懶人配置

路由添加好了翎迁,還可以考慮一下使用情況如下:

<?php
$router = new Router();

$router->get('/', ['controller' => '\APP\Controller\HomeController', 'action' => 'indexAction']);
$router->get('/version', ['controller' => '\APP\Controller\HomeController', 'action' => 'versionAction']);
$router->get('/about', ['controller' => '\APP\Controller\HomeController', 'action' => 'aboutAction']);

我們可以看到每個(gè)控制器都有一個(gè)后綴 Controller 和命名空間栋猖,每個(gè)方法都有一個(gè)后綴 Action。(這個(gè)與實(shí)際項(xiàng)目規(guī)劃有關(guān))

可以想象汪榔,如果有一堆路由蒲拉,我們就得寫一堆的 Controller、命名空間 和 Action揍异。

懶人有懶招全陨,我們只需要給它兩個(gè)接口,去定義后綴是什么衷掷、命名空間是什么辱姨,就不需要每次都手打 Controller、命名空間 和 Action 了戚嗅。

<?php
use Request;
class Router
{
    /**
     * 命名空間
     * 
     * @var string
     */
    protected $_namespace;

    /**
     * 控制器后綴
     * 
     * @var string
     */
    protected $_controllerSuffix;

    /**
     * 方法后綴
     * 
     * @var string
     */
    protected $_actionSuffix;

    ...

    /**
     * 設(shè)置命名空間
     * 
     * @param string $namespace
     */
    public function setNamespace($namespace)
    {
        $namespace = (string)$namespace;
        $namespace = trim($namespace, '\\');
        $this->_namespace = sprintf("\\%s", $namespace);
    }

    /**
     * 設(shè)置控制器后綴
     * 
     * @param string $suffix
     */
    public function setControllerSuffix($suffix)
    {
        $this->_controllerSuffix = (string)$suffix;
    }

    /**
     * 設(shè)置方法后綴
     * 
     * @param string $suffix
     */
    public function setActionSuffix($suffix)
    {
        $this->_actionSuffix = (string)$suffix;
    }
}

默認(rèn)路由

再思考一下雨涛,如果用戶發(fā)了一個(gè)請求,而這個(gè)請求的路由并沒有定義懦胞,這個(gè)時(shí)候替久,我們不報(bào)錯(cuò),希望它能夠到一個(gè)指定的頁面躏尉,改怎么做蚯根?

默認(rèn)路由!U兔印颅拦!

<?php
use Request;
class Router
{
    /**
     * 默認(rèn)路由
     * 
     * @var array
     */
    protected $_default = [];

    ...


    /**
     * 設(shè)置默認(rèn)控制器
     * 
     * @param string $controller
     */
    public function setDefaultController($controller)
    {
        $this->_default['controller'] = (string)$controller;
    }

    /**
     * 設(shè)置默認(rèn)方法
     * 
     * @param string $action
     */
    public function setDefaultAction($action)
    {
        $this->_default['action'] = (string)$action;
    }

    /**
     * 設(shè)置默認(rèn)參數(shù)
     * 
     * @param string $params
     */
    public function setDefaultParams(array $params)
    {
        $this->_default['params'] = $params;
    }
}

解析

所有配置都完成之后蒂誉,下面就涉及到解析路由了。

我們在存儲(chǔ)路由的時(shí)候距帅,用了一個(gè)數(shù)組右锨。不同請求類型的路由存放在不同的列表當(dāng)中,如果我們需要篩選出我們想要的路由碌秸,就需要對這個(gè)列表進(jìn)行遍歷绍移。當(dāng)然,如果想要減少遍歷次數(shù)讥电,也不是很難蹂窖,可以參考自動(dòng)加載模塊別名的設(shè)置與匹配。

過程分析:

1允趟、獲取當(dāng)前請求的路由
2恼策、獲取當(dāng)前請求方式
3、獲取此請求方式的列表和 any 列表
4潮剪、匹配路由
5涣楷、獲取匹配結(jié)果并存儲(chǔ)

<?php
use Request;
class Router
{
    /**
     * 匹配路由
     * 
     * @var array
     */
    protected $_matchRoute = [];

    ...


    /**
     * 路由解析
     * 
     * @return void
     */
    public function render()
    {
        // 獲取當(dāng)前請求的路由
        $uri = $request->getServer('REQUEST_URI');
        $baseUri = $this->parse2BaseUri($uri);

        // 獲取當(dāng)前請求方式
        $request = new Request();
        $method = $request->getMethod();

        // 獲取此請求方式的列表并匹配
        $rules = [];
        if (isset($this->_routes[$method])) {
            $route = $this->getRules($method, $baseUri);
        }
        // 若沒有匹配到,則匹配 any 列表
        if (!$route && isset($this->_routes[self::METHOD_ANY])) {
            $route = $this->getRules(self::METHOD_ANY, $baseUri);
        }

        // 如果還是沒有匹配到抗碰,取默認(rèn)路由
        if (!$route) {
            $this->_matchRoute = $this->getDefaultMatchRoute();
            return true;
        }

        // 獲取匹配到的信息
        $controller = $route->getControllerName();
        $controller = ltrim($controller, '\\');
        $action = $route->getActionName();
        $params = $route->getParams();

        // 拼湊結(jié)果
        $this->_matchRoute = $this->formatMatched($controller, $action, $params);
    }

    /**
     * 匹配路由
     * 
     * @param  string $method
     * @param  string $uri
     * @return mixed
     */
    protected function getRules($method, $uri)
    {
        foreach ($this->_routes[$method] as $route) {
            if ($route->match($uri)) {
                return $route;
            }
        }
        return false;
    }

    /**
     * 獲取 base uri
     * 
     * @param  string $uri
     * @return string
     */
    protected function parse2BaseUri($uri)
    {
        if (!$uri) {
            return '/';
        }
        $uriParts = explode('?', $uri);
        $uri = reset($uriParts);
        return $uri ? $uri : '/';
    }

    /**
     * 獲取默認(rèn)匹配
     * 
     * @throws \Exception
     * @return array
     */
    protected function getDefaultMatchRoute()
    {
        $default = $this->_default;
        if (!$default
            || !isset($default['controller']) || !$default['controller']
            || !isset($default['action']) || !$default['action']) {
            throw new \Exception('您訪問的網(wǎng)頁不存在', -__LINE__);
        }
        $params = isset($default['params']) ? $default['params'] : [];
        return $this->formatMatched($default['controller'], $default['action'], $params);
    }

    /**
     * 匹配結(jié)果格式化
     * 
     * @param  string $controller
     * @param  string $action
     * @param  array  $params
     * @return array
     */
    protected function formatMatched($controller, $action, $params = [])
    {
        return [
            'controller' => sprintf("%s\\%s%s", $this->_namespace, $controller, $this->_controllerSuffix),
            'action' => sprintf("%s%s", $action, $this->_actionSuffix),
            'params' => $params
        ];
    }
}

獲取匹配結(jié)果

<?php
use Request;
class Router
{
    /**
     * 獲取匹配的路由
     * 
     * @return array
     */
    public function getMatchRoute()
    {
        return $this->_matchRoute;
    }
}

路由定義解析類

在上面使用路由定義解析類的時(shí)候狮斗,我沒有多做解釋,主要是解釋之后弧蝇,就會(huì)有點(diǎn)混亂碳褒,還不如直接放到后面來介紹。

在使用過程中看疗,我們涉及到了幾個(gè)方法 __construct沙峻、matchgetControllerName两芳、getActionName摔寨、getParams

現(xiàn)在開始介紹最后三個(gè)怖辆。

構(gòu)造函數(shù)

<?php
$router->get('/', ['controller' => 'Home', 'action' => 'index']);
$router->get('/articles/{gallery:[a-zA-Z]+}', ['controller' => 'Article', 'action' => 'list']);

從路由定義的角度來看是复,我們的構(gòu)造函數(shù)需要兩個(gè)參數(shù),分別是路由和路由信息竖螃。

過程分析:

1淑廊、設(shè)置路由和相關(guān)參數(shù)
2、如果路由里不包含正則特咆,則賦值類屬性
3季惩、如果路由里包含正則,則解析并返回路由正則表達(dá)式(這里用了PHP子組模式,有興趣的小伙伴可以看看)

<?php
class Route
{
    /**
     * 原始路由
     * 
     * @var string
     */
    protected $_pattern = '';

    /**
     * 解析后的正則表達(dá)式
     * 
     * @var string
     */
    protected $_compilePattern = '';

    /**
     * 路由正則參數(shù)
     * 
     * @var array
     */
    protected $_params = [];

    /**
     * 路由信息
     * 
     * @var array
     */
    protected $_rules = [
        'controller' => '',
        'action' => '',
        'params' => []
    ];

    /**
     * 構(gòu)造函數(shù)
     * 
     * @param string $pattern
     * @param array  $rules
     */
    function __construct($pattern, array $rules)
    {
        $pattern = (string)$pattern;
        // 解析路由規(guī)則
        $this->reConfigure($pattern);
        $this->_rules['controller'] = isset($rules['controller']) ? $rules['controller'] : '';
        $this->_rules['action'] = isset($rules['action']) ? $rules['action'] : '';
        $this->_rules['params'] = isset($rules['params']) ? $rules['params'] : [];
    }

    /**
     * 解析路由規(guī)則
     * 
     * @param  string $pattern
     */
    public function reConfigure($pattern)
    {
        if (!$pattern) {
            throw new \Exception('路由規(guī)則不能為空', -__LINE__);
        }

        // 如果存在正則蜀备,則需要解析正則
        if (strpos($pattern, '{') !== false) {
            $prePattern = $this->extractNamePattern($pattern);
            $this->_compilePattern = $prePattern;
        } else {
            $this->_compilePattern = $pattern;
        }
        $this->_pattern = $pattern;
    }

    /**
     * 解析參數(shù)正則
     * 
     * @param  string $pattern
     * @return string
     */
    public function extractNamePattern($pattern)
    {
        $callback = function ($match) {
            if (!isset($match[1])) {
                return '';
            }
            $params = explode(':', $match[1]);
            if (!isset($params[1])) {
                return '';
            }
            list($ident, $pattern) = $params;
            $this->_params[] = [
                'ident' => $ident,
                'pattern' => $pattern
            ];
            return sprintf("(?'%s'%s)", $ident, $pattern);
        };
        return preg_replace_callback('/\B{([^}]+)}/x', $callback, $pattern);
    }
}

獲取匹配參數(shù)

匹配好一個(gè)路由关摇,我們需要知道調(diào)用哪個(gè)控制器,哪個(gè)方法碾阁。所以這里需要解析類提供解析后的結(jié)果。

這里還有一個(gè)沒有做的些楣,那就是模塊脂凶。目前我自己寫的項(xiàng)目中,不同的模塊是使用域名來區(qū)分的愁茁,如果有需要可以后續(xù)加上蚕钦,也并不麻煩。

<?php
class Route
{
    /**
     * 路由信息
     * 
     * @var array
     */
    protected $_rules = [
        'controller' => '',
        'action' => '',
        'params' => []
    ];

    /**
     * 獲取控制器名稱
     * 
     * @return string
     */
    public function getControllerName()
    {
        return $this->_rules['controller'];
    }

    /**
     * 獲取方法名稱
     * 
     * @return string
     */
    public function getActionName()
    {
        return $this->_rules['action'];
    }

    /**
     * 獲取參數(shù)
     * 
     * @return array
     */
    public function getParams()
    {
        return $this->_rules['params'];
    }
}

匹配

過程分析:

1鹅很、如果請求路由和定義路由一致嘶居,直接返回真
2、不一致則獲取路由匹配正則促煮,查看是否匹配
3邮屁、如果匹配,則將匹配到的內(nèi)容拼湊成參數(shù)

<?php
class Route
{
    /**
     * 匹配
     * 
     * @param  string $uri
     * @return bool
     */
    public function match($uri)
    {
        // 如果路由和定義的一致菠齿,則直接返回真
        if ($this->_pattern == $uri) {
            return true;
        }

        // 拼湊正則
        $preg = sprintf('#^%s$#u', $this->_compilePattern);
        $matches = [];

        // 匹配正則
        if (preg_match($preg, $uri, $matches) == false) {
            return false;
        }

        // 拼湊參數(shù)
        foreach ($this->_params as $param) {
            $ident = $param['ident'];
            $this->_rules['params'][] = isset($matches[$ident]) ? $matches[$ident] : '';
        }

        return true;
    }
}

完整代碼

路由設(shè)置類

<?php
namespace Terse\Mvc;
use Terse\Http\Request;
use Terse\Mvc\Router\Route;
/**
* Terse\Mvc\Router
* 
* 路由設(shè)置
* 
* @link https://gitee.com/imjcw/terse
* @author imjcw <imjcw@imjcw.com>
*/
class Router
{
    const METHOD_ANY = 'any';

    /**
     * 路由信息
     * 
     * @var array
     */
    protected $_routes  [];

    /**
     * 命名空間
     * 
     * @var string
     */
    protected $_namespace = '';

    /**
     * 控制器后綴
     * 
     * @var string
     */
    protected $_controllerSuffix = '';

    /**
     * 方法后綴
     * 
     * @var string
     */
    protected $_actionSuffix = '';

    /**
     * 匹配路由
     * 
     * @var array
     */
    protected $_matchRoute = [];

    /**
     * 默認(rèn)路由
     * 
     * @var array
     */
    protected $_default = [];

    /**
     * 構(gòu)造函數(shù)
     */
    function __construct()
    {
        $this->_matchRoute = [];
        $this->_namespace = '';
        $this->_controllerSuffix = '';
        $this->_actionSuffix = '';
    }

    /**
     * 設(shè)置命名空間
     * 
     * @param string $namespace
     */
    public function setNamespace($namespace)
    {
        $namespace = (string)$namespace;
        $namespace = trim($namespace, '\\');
        $this->_namespace = sprintf("\\%s", $namespace);
    }

    /**
     * 設(shè)置控制器后綴
     * 
     * @param string $suffix
     */
    public function setControllerSuffix($suffix)
    {
        $this->_controllerSuffix = (string)$suffix;
    }

    /**
     * 設(shè)置方法后綴
     * 
     * @param string $suffix
     */
    public function setActionSuffix($suffix)
    {
        $this->_actionSuffix = (string)$suffix;
    }

    /**
     * 設(shè)置默認(rèn)控制器
     * 
     * @param string $controller
     */
    public function setDefaultController($controller)
    {
        $this->_default['controller'] = (string)$controller;
    }

    /**
     * 設(shè)置默認(rèn)方法
     * 
     * @param string $action
     */
    public function setDefaultAction($action)
    {
        $this->_default['action'] = (string)$action;
    }

    /**
     * 設(shè)置默認(rèn)參數(shù)
     * 
     * @param string $params
     */
    public function setDefaultParams(array $params)
    {
        $this->_default['params'] = $params;
    }

    /**
     * 設(shè)置請求方式為GET的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function get($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_GET);
        return $this;
    }

    /**
     * 設(shè)置請求方式為POST的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function post($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_POST);
        return $this;
    }

    /**
     * 設(shè)置請求方式為PUT的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function put($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_PUT);
        return $this;
    }

    /**
     * 設(shè)置請求方式為DELETE的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function delete($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_DELETE);
        return $this;
    }

    /**
     * 設(shè)置請求方式為HEAD的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function head($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_HEAD);
        return $this;
    }

    /**
     * 設(shè)置請求方式為OPTIONS的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function options($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_OPTIONS);
        return $this;
    }

    /**
     * 設(shè)置請求方式為PATCH的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function patch($uri, array $rules)
    {
        $this->add($uri, $rules, Request::METHOD_PATCH);
        return $this;
    }

    /**
     * 設(shè)置任意請求方式的規(guī)則
     * 
     * @param  string $uri
     * @param  array  $rules
     * @return \Terse\Mvc\Router
     */
    public function any($uri, array $rules)
    {
        $this->add($uri, $rules, self::METHOD_ANY);
        return false;
    }

    /**
     * 路由解析
     * 
     * @return void
     */
    public function render()
    {
        // 獲取當(dāng)前請求的路由
        $uri = $request->getServer('REQUEST_URI');
        $baseUri = $this->parse2BaseUri($uri);

        // 獲取當(dāng)前請求方式
        $request = new Request();
        $method = $request->getMethod();

        // 獲取此請求方式的列表并匹配
        $rules = [];
        if (isset($this->_routes[$method])) {
            $route = $this->getRules($method, $baseUri);
        }
        // 若沒有匹配到佑吝,則匹配 any 列表
        if (!$route && isset($this->_routes[self::METHOD_ANY])) {
            $route = $this->getRules(self::METHOD_ANY, $baseUri);
        }

        // 如果還是沒有匹配到,取默認(rèn)路由
        if (!$route) {
            $this->_matchRoute = $this->getDefaultMatchRoute();
            return true;
        }

        // 獲取匹配到的信息
        $controller = $route->getControllerName();
        $controller = ltrim($controller, '\\');
        $action = $route->getActionName();
        $params = $route->getParams();

        // 拼湊結(jié)果
        $this->_matchRoute = $this->formatMatched($controller, $action, $params);
    }

    /**
     * 獲取匹配的路由
     * 
     * @return array
     */
    public function getMatchRoute()
    {
        return $this->_matchRoute;
    }

    /**
     * 添加一種規(guī)則
     * 
     * @param string $uri
     * @param array  $rules
     * @param string $method
     */
    protected function add($uri, array $rules, $method)
    {
        $this->_routes[$method][] = new Route($uri, $rules);
    }

    /**
     * 匹配路由
     * 
     * @param  string $method
     * @param  string $uri
     * @return mixed
     */
    protected function getRules($method, $uri)
    {
        foreach ($this->_routes[$method] as $route) {
            if ($route->match($uri)) {
                return $route;
            }
        }
        return false;
    }

    /**
     * 獲取 base uri
     * 
     * @param  string $uri
     * @return string
     */
    protected function parse2BaseUri($uri)
    {
        if (!$uri) {
            return '/';
        }
        $uriParts = explode('?', $uri);
        $uri = reset($uriParts);
        return $uri ? $uri : '/';
    }

    /**
     * 獲取默認(rèn)匹配
     * 
     * @throws \Exception
     * @return array
     */
    protected function getDefaultMatchRoute()
    {
        $default = $this->_default;
        if (!$default
            || !isset($default['controller']) || !$default['controller']
            || !isset($default['action']) || !$default['action']) {
            throw new \Exception('您訪問的網(wǎng)頁不存在', -__LINE__);
        }
        $params = isset($default['params']) ? $default['params'] : [];
        return $this->formatMatched($default['controller'], $default['action'], $params);
    }

    /**
     * 匹配結(jié)果格式化
     * 
     * @param  string $controller
     * @param  string $action
     * @param  array  $params
     * @return array
     */
    protected function formatMatched($controller, $action, $params = [])
    {
        return [
            'controller' => sprintf("%s\\%s%s", $this->_namespace, $controller, $this->_controllerSuffix),
            'action' => sprintf("%s%s", $action, $this->_actionSuffix),
            'params' => $params
        ];
    }
}

路由定義類

<?php
namespace Terse\Mvc\Router;
/**
* Terse\Mvc\Router\Route
* 
* 路由規(guī)則解析和匹配
* 
* @link https://gitee.com/imjcw/terse
* @author imjcw <imjcw@imjcw.com>
*/
class Route
{
    /**
     * 原始路由
     * 
     * @var string
     */
    protected $_pattern = '';

    /**
     * 解析后的正則表達(dá)式
     * 
     * @var string
     */
    protected $_compilePattern = '';

    /**
     * 路由正則參數(shù)
     * 
     * @var array
     */
    protected $_params = [];

    /**
     * 路由信息
     * 
     * @var array
     */
    protected $_rules = [
        'controller' => '',
        'action' => '',
        'params' => []
    ];

    /**
     * 構(gòu)造函數(shù)
     * 
     * @param string $pattern
     * @param array  $rules
     */
    function __construct($pattern, array $rules)
    {
        $pattern = (string)$pattern;
        // 解析路由規(guī)則
        $this->reConfigure($pattern);
        $this->_rules['controller'] = isset($rules['controller']) ? $rules['controller'] : '';
        $this->_rules['action'] = isset($rules['action']) ? $rules['action'] : '';
        $this->_rules['params'] = isset($rules['params']) ? $rules['params'] : [];
    }

    /**
     * parse pattern
     * 
     * @param  string $pattern
     */
    public function reConfigure($pattern)
    {
        if (!$pattern) {
            throw new \Exception('pattern 不能為空', -__LINE__);
        }

        // 如果存在正則绳匀,則需要解析正則
        if (strpos($pattern, '{') !== false) {
            $prePattern = $this->extractNamePattern($pattern);
            $this->_compilePattern = $prePattern;
        } else {
            $this->_compilePattern = $pattern;
        }
        $this->_pattern = $pattern;
    }

    /**
     * 解析參數(shù)正則
     * 
     * @param  string $pattern
     * @return string
     */
    public function extractNamePattern($pattern)
    {
        $callback = function ($match) {
            if (!isset($match[1])) {
                return '';
            }
            $params = explode(':', $match[1]);
            if (!isset($params[1])) {
                return '';
            }
            list($ident, $pattern) = $params;
            $this->_params[] = [
                'ident' => $ident,
                'pattern' => $pattern
            ];
            return sprintf("(?'%s'%s)", $ident, $pattern);
        };
        return preg_replace_callback('/\B{([^}]+)}/x', $callback, $pattern);
    }

    /**
     * 匹配
     * 
     * @param  string $uri
     * @return bool
     */
    public function match($uri)
    {
        // 如果路由和定義的一致芋忿,則直接返回真
        if ($this->_pattern == $uri) {
            return true;
        }

        // 拼湊正則
        $preg = sprintf('#^%s$#u', $this->_compilePattern);
        $matches = [];

        // 匹配正則
        if (preg_match($preg, $uri, $matches) == false) {
            return false;
        }

        // 拼湊參數(shù)
        foreach ($this->_params as $param) {
            $ident = $param['ident'];
            $this->_rules['params'][] = isset($matches[$ident]) ? $matches[$ident] : '';
        }

        return true;
    }

    /**
     * 獲取控制器名稱
     * 
     * @return string
     */
    public function getControllerName()
    {
        return isset($this->_rules['controller']) ? $this->_rules['controller'] : '';
    }

    /**
     * 獲取方法名稱
     * 
     * @return string
     */
    public function getActionName()
    {
        return isset($this->_rules['action']) ? $this->_rules['action'] : '';
    }

    /**
     * 獲取參數(shù)
     * 
     * @return array
     */
    public function getParams()
    {
        return isset($this->_rules['params']) ? $this->_rules['params'] : [];
    }
}

最后

這一篇沒有介紹太多,主要是細(xì)節(jié)比較多疾棵,直接讀代碼要比解釋的效果要好多了戈钢。

下一篇《中間件模塊》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市是尔,隨后出現(xiàn)的幾起案子殉了,更是在濱河造成了極大的恐慌,老刑警劉巖嗜历,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宣渗,死亡現(xiàn)場離奇詭異,居然都是意外死亡梨州,警方通過查閱死者的電腦和手機(jī)痕囱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來暴匠,“玉大人鞍恢,你說我怎么就攤上這事。” “怎么了帮掉?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵弦悉,是天一觀的道長。 經(jīng)常有香客問我蟆炊,道長稽莉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任涩搓,我火速辦了婚禮污秆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘昧甘。我一直安慰自己良拼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布充边。 她就那樣靜靜地躺著庸推,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浇冰。 梳的紋絲不亂的頭發(fā)上贬媒,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音湖饱,去河邊找鬼掖蛤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛井厌,可吹牛的內(nèi)容都是我干的蚓庭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼仅仆,長吁一口氣:“原來是場噩夢啊……” “哼器赞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起墓拜,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤港柜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后咳榜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體夏醉,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年涌韩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了畔柔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡臣樱,死狀恐怖靶擦,靈堂內(nèi)的尸體忽然破棺而出腮考,到底是詐尸還是另有隱情,我是刑警寧澤玄捕,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布踩蔚,位于F島的核電站,受9級(jí)特大地震影響枚粘,放射性物質(zhì)發(fā)生泄漏馅闽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一赌结、第九天 我趴在偏房一處隱蔽的房頂上張望捞蛋。 院中可真熱鬧,春花似錦柬姚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至穴店,卻和暖如春撕捍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泣洞。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工忧风, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人球凰。 一個(gè)月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓狮腿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親呕诉。 傳聞我的和親對象是個(gè)殘疾皇子缘厢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容