PHP框架之简单的路由器
作者: default|标签:PHP 路由器 简单|2017-11-13 13:41
路由的功能就是分发请求到不同的控制器,基于的原理就是正则匹配。接下来呢,我们实现一个简单的路由器,实现的能力是对于静态的路由(没占位符的),正确调用callback。

对于有占位符的路由,正确调用callback时传入占位符参数,譬如对于路由:/user/{id},当请求为/user/23时,传入参数$args结构为

[    'id' => '23'
]

大致思路

我们需要把每个路由的信息管理起来:http方法($method),路由字符串($route),回调($callback),因此需要一个addRoute方法,另外提供短方法get,post(就是把$method写好)

对于/user/{id}这样的有占位符的路由字符串,把占位符要提取出来,然后占位符部分变成正则字符串

实现

Route.php类

<?phpnamespace SalamanderRoute;class Route {    /** @var string */
    public $httpMethod;    /** @var string */
    public $regex;    /** @var array */
    public $variables;    /** @var mixed */
    public $handler;    /**
     * Constructs a route (value object).
     *
     * @param string $httpMethod
     * @param mixed  $handler
     * @param string $regex
     * @param array  $variables
     */
    public function __construct($httpMethod, $handler, $regex, $variables) {        $this->httpMethod = $httpMethod;        $this->handler = $handler;      
      $this->regex = $regex;        $this->variables = $variables;
    }    /**
     * Tests whether this route matches the given string.
     *
     * @param string $str
     *
     * @return bool
     */
    public function matches($str) {
        $regex = '~^' . $this->regex . '$~';        return (bool) preg_match($regex, $str);
    }
}

Dispatcher.php

<?php/**
 * User: salamander
 * Date: 2017/11/12
 * Time: 13:43
 */namespace SalamanderRoute;class Dispatcher {    /** @var mixed[][] */
    protected $staticRoutes = [];    /** @var Route[][] */
    private $methodToRegexToRoutesMap = [];    const NOT_FOUND = 0;    const FOUND = 1;    const METHOD_NOT_ALLOWED = 2;    /**
     * 提取占位符
     * @param $route
     * @return array
     */
    private function parse($route) {
        $regex = '~^(?:/[a-zA-Z0-9_]*|/\{([a-zA-Z0-9_]+?)\})+/?$~';        if(preg_match($regex, $route, $matches)) {            // 去掉full match
            array_shift($matches);            return [
                preg_replace('~{[a-zA-Z0-9_]+?}~', '([a-zA-Z0-9_]+)', $route),
                $matches,
            ];
        }        throw new \LogicException('register route failed, pattern is illegal');
    }    /**
     * 注册路由
     * @param $httpMethod string | string[]
     * @param $route
     * @param $handler
     */
    public function addRoute($httpMethod, $route, $handler) {
        $routeData = $this->parse($route);        foreach ((array) $httpMethod as $method) {            if ($this->isStaticRoute($routeData)) {                $this->addStaticRoute($httpMethod, $routeData, $handler);
            } else {                $this->addVariableRoute($httpMethod, $routeData, $handler);
            }
        }
    }    private function isStaticRoute($routeData) {        return count($routeData[1]) === 0;
    }    private function addStaticRoute($httpMethod, $routeData, $handler) {
        $routeStr = $routeData[0];        if (isset($this->staticRoutes[$httpMethod][$routeStr])) {            throw new \LogicException(sprintf(                'Cannot register two routes matching "%s" for method "%s"',
                $routeStr, $httpMethod
            ));
        }        if (isset($this->methodToRegexToRoutesMap[$httpMethod])) {            foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) {                if ($route->matches($routeStr)) {                    throw new \LogicException(sprintf(                        'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"',
                        $routeStr, $route->regex, $httpMethod
                    ));
                }
            }
        }        $this->staticRoutes[$httpMethod][$routeStr] = $handler;
    }    private function addVariableRoute($httpMethod, $routeData, $handler) {        list($regex, $variables) = $routeData;        if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) {            throw new \LogicException(sprintf(                'Cannot register two routes matching "%s" for method "%s"',
                $regex, $httpMethod
            ));
        }        $this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route(
            $httpMethod, $handler, $regex, $variables
        );
    }    public function get($route, $handler) {        $this->addRoute('GET', $route, $handler);
    }    public function post($route, $handler) {        $this->addRoute('POST', $route, $handler);
    }    public function put($route, $handler) {        $this->addRoute('PUT', $route, $handler);
    }    public function delete($route, $handler) {        $this->addRoute('DELETE', $route, $handler);
    }    public function patch($route, $handler) {        $this->addRoute('PATCH', $route, $handler);
    }    public function head($route, $handler) {        $this->addRoute('HEAD', $route, $handler);
    }    /**
     * 分发
     * @param $httpMethod
     * @param $uri
     */
    public function dispatch($httpMethod, $uri) {
        $staticRoutes = array_keys($this->staticRoutes[$httpMethod]);        foreach ($staticRoutes as $staticRoute) {            if($staticRoute === $uri) {                return [self::FOUND, $this->staticRoutes[$httpMethod][$staticRoute], []];
            }
        }

        $routeLookup = [];
        $index = 1;
        $regexes = array_keys($this->methodToRegexToRoutesMap[$httpMethod]);        foreach ($regexes as $regex) {
            $routeLookup[$index] = [                $this->methodToRegexToRoutesMap[$httpMethod][$regex]->handler,                $this->methodToRegexToRoutesMap[$httpMethod][$regex]->variables,
            ];
            $index += count($this->methodToRegexToRoutesMap[$httpMethod][$regex]->variables);
        }
        $regexCombined = '~^(?:' . implode('|', $regexes) . ')$~';        if(!preg_match($regexCombined, $uri, $matches)) {            return [self::NOT_FOUND];
        }        for ($i = 1; '' === $matches[$i]; ++$i);        list($handler, $varNames) = $routeLookup[$i];
        $vars = [];        foreach ($varNames as $varName) {
            $vars[$varName] = $matches[$i++];
        }        return [self::FOUND, $handler, $vars];
    }
}

配置

nginx.conf重写到index.php

location / {        try_files $uri $uri/ /index.php$is_args$args;        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 
       #        location ~ \.php$ {            fastcgi_pass   127.0.0.1:9000;            fastcgi_index  index.php;        
           fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;            include        fastcgi_params;        }    }
composer.json自动载入
{    "name": "salmander/route",    "require": {},    "autoload": {      "psr-4": 
{        "SalamanderRoute\\": "SalamanderRoute/"      } 
 }}

composer.json自动载入

{    "name": "salmander/route",    "require": {},    "autoload": {      "psr-4": 
{        "SalamanderRoute\\": "SalamanderRoute/"      }  }

最终使用

index.php

<?phpinclude_once 'vendor/autoload.php';use SalamanderRoute\Dispatcher;
$dispatcher = new Dispatcher();
$dispatcher->get('/', function () {    echo 'hello world';
});
$dispatcher->get('/user/{id}', function ($args) {    echo "user {$args['id']} visit";
});// Fetch method and URI from somewhere$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];// 去掉查询字符串if (false !== $pos = strpos($uri, '?')) {
    $uri = substr($uri, 0, $pos);
}
$routeInfo = $dispatcher->dispatch($httpMethod, $uri);switch ($routeInfo[0]) {    case Dispatcher::NOT_FOUND:        echo '404 not found';        break;    case Dispatcher::FOUND:
        $handler = $routeInfo[1];
        $vars = $routeInfo[2];
        $handler($vars);        break;
}

看了上面的这个案例大家应该对PHP实现简单路由器,有更清楚的认识吧,后期我们还会继续推出相关文章,大家有任何问题都可以踊跃发言,

相关推荐:

JS实现简单路由器功能的方法

怎样修改无线路由器密码 MySQL修改密码方法总结

php yaf框架中路由器问题

以上就是PHP框架之简单的路由器的详细内容,更多请关注php中文网其它相关文章!

87
点赞
收藏
分享:
0/300