资讯专栏INFORMATION COLUMN

php微框架 flight源码阅读——3.路由Router实现及执行过程

王晗 / 2580人阅读

摘要:当然在对象中也没有方法,于是会触发当前对象中的魔术方法。获取对象获取对象获取对象设置方法执行的后置操作现在来看操作都做了什么。匹配的部分对路由匹配实现正则匹配微框架源码阅读系列

现在来分析路由实现及执行过程,在项目目录下创建index.php,使用文档中的路由例子(含有路由规则匹配),如下:


首先引入"flight/Flight.php"框架入口文件,在执行Flight::route()时当然在Flight类中找不到该方法,于是就会调用下面的__callStatic()魔术方法,然后去执行flightcoreDispatcher::invokeMethod(array($app, $name), $params),其中$app就是之前框架初始化好后的Engine类实例化对象,$params就是定义路由传入的参数:匹配规则或url和一个匿名回调函数。

/**
 * Handles calls to static methods.
 *
 * @param string $name Method name
 * @param array $params Method parameters
 * @return mixed Callback results
 * @throws Exception
 */
public static function __callStatic($name, $params) {
    $app = Flight::app();
    
    return flightcoreDispatcher::invokeMethod(array($app, $name), $params);
}

接着会调用Dispatcher类的invokeMethod()方法,$class$method分别对应刚才的$app对象和$name参数。is_object($class)返回true,很明显count($params)值为2,因此会执行case语句中的$class->$method($params[0], $params[1]),就是去Engine对象中调用route()方法。

 /**
 * Invokes a method.
 *
 * @param mixed $func Class method
 * @param array $params Class method parameters
 * @return mixed Function results
 */
public static function invokeMethod($func, array &$params = array()) {
    list($class, $method) = $func;

    $instance = is_object($class);
   
    switch (count($params)) {
        case 0:
            return ($instance) ?
                $class->$method() :
                $class::$method();
        case 1:
            return ($instance) ?
                $class->$method($params[0]) :
                $class::$method($params[0]);
        case 2:
            return ($instance) ?
                $class->$method($params[0], $params[1]) :
                $class::$method($params[0], $params[1]);
        case 3:
            return ($instance) ?
                $class->$method($params[0], $params[1], $params[2]) :
                $class::$method($params[0], $params[1], $params[2]);
        case 4:
            return ($instance) ?
                $class->$method($params[0], $params[1], $params[2], $params[3]) :
                $class::$method($params[0], $params[1], $params[2], $params[3]);
        case 5:
            return ($instance) ?
                $class->$method($params[0], $params[1], $params[2], $params[3], $params[4]) :
                $class::$method($params[0], $params[1], $params[2], $params[3], $params[4]);
        default:
            return call_user_func_array($func, $params);
    }
}

当然在Engine对象中也没有route()方法,于是会触发当前对象中的__call()魔术方法。在这个方法中通过$this->dispatcher->get($name)去获取框架初始化时设置的Dispatcher对象的$events属性:$this->dispatcher->set($name, array($this, "_".$name)),然后$events属性数组中会有一个route键名对应的值为[$this Engine对象, "_route"]数组,返回的$callback=[$this Engine对象, "_route"]并且is_callable($callback)==true

 /**
 * Handles calls to class methods.
 *
 * @param string $name Method name
 * @param array $params Method parameters
 * @return mixed Callback results
 * @throws Exception
 */
public function __call($name, $params) {
    $callback = $this->dispatcher->get($name);

    if (is_callable($callback)) {
        return $this->dispatcher->run($name, $params);
    }

    if (!$this->loader->get($name)) {
        throw new Exception("{$name} must be a mapped method.");
    }

    $shared = (!empty($params)) ? (bool)$params[0] : true;

    return $this->loader->load($name, $shared);
}

那么,接着就该执行$this->dispatcher->run($name, $params),那就看下Dispatcher对象中的run()方法,由于框架初始化时没有对route()方法进行设置前置和后置操作,所以直接执行$this->execute($this->get($name), $params)

/**
 * Dispatches an event.
 *
 * @param string $name Event name
 * @param array $params Callback parameters
 * @return string Output of callback
 * @throws Exception
 */
public function run($name, array $params = array()) {
    $output = "";

    // Run pre-filters
    if (!empty($this->filters[$name]["before"])) {
        $this->filter($this->filters[$name]["before"], $params, $output);
    }

    // Run requested method
    $output = $this->execute($this->get($name), $params);

    // Run post-filters
    if (!empty($this->filters[$name]["after"])) {
        $this->filter($this->filters[$name]["after"], $params, $output);
    }

    return $output;
}

接着来看Dispatcher对象中的execute方法,因为is_callable($callback)==true && is_array($callback),所以又再次调用self::invokeMethod($callback, $params)

/**
 * Executes a callback function.
 *
 * @param callback $callback Callback function
 * @param array $params Function parameters
 * @return mixed Function results
 * @throws Exception
 */
public static function execute($callback, array &$params = array()) {
    if (is_callable($callback)) {
        return is_array($callback) ?
            self::invokeMethod($callback, $params) :
            self::callFunction($callback, $params);
    }
    else {
        throw new Exception("Invalid callback specified.");
    }
}

但是这次调用invokeMethod方法跟刚才有所不同,刚才的$callback是[$app, "route"],现在的$callback[$this Engine对象, "_route"]$params是一样的。然后invokeMethod方法中的$class$this Engine对象$method为"_route",is_object($class)为true。然后再执行$class->$method($params[0], $params[1]),这次在Engine对象中就可以调用到_route方法了。

接着来看Engine对象的_route()方法做了什么。$this->router()会触发当前对象的__call()魔术方法,根据刚才的分析$this->dispatcher->get($name)返回null。而$this->loader->get($name)返回true,然后就去执行$this->loader->load($name, $shared)。在Load对象的load方法中isset($this->classes[$name])为true,isset($this->instances[$name])返回false,在框架初始化时设置的$params$backcall都为默认值,所以会执行$this->newInstance($class, $params),在newInstance方法中直接return new $class()。总结:$this->router()其实就是通过工厂模式去实例化框架初始化时所设置的"flight etRouter"类,依次论推$this->request()、$this->response()、$this->view()是一样的逻辑。

flight/Engine.php

/**
 * Routes a URL to a callback function.
 *
 * @param string $pattern URL pattern to match
 * @param callback $callback Callback function
 * @param boolean $pass_route Pass the matching route object to the callback
 */
public function _route($pattern, $callback, $pass_route = false) {
    $this->router()->map($pattern, $callback, $pass_route);
}

flight/core/Loader.php

 /**
 * Loads a registered class.
 *
 * @param string $name Method name
 * @param bool $shared Shared instance
 * @return object Class instance
 * @throws Exception
 */
public function load($name, $shared = true) {
    $obj = null;

    if (isset($this->classes[$name])) {
        list($class, $params, $callback) = $this->classes[$name];

        $exists = isset($this->instances[$name]);
        
        if ($shared) {
            $obj = ($exists) ?
                $this->getInstance($name) :
                $this->newInstance($class, $params);
            
            if (!$exists) {
                $this->instances[$name] = $obj;
            }
        }
        else {
            $obj = $this->newInstance($class, $params);
        }

        if ($callback && (!$shared || !$exists)) {
            $ref = array(&$obj);
            call_user_func_array($callback, $ref);
        }
    }

    return $obj;
}

/**
 * Gets a new instance of a class.
 *
 * @param string|callable $class Class name or callback function to instantiate class
 * @param array $params Class initialization parameters
 * @return object Class instance
 * @throws Exception
 */
public function newInstance($class, array $params = array()) {
    if (is_callable($class)) {
        return call_user_func_array($class, $params);
    }

    switch (count($params)) {
        case 0:
            return new $class();
        case 1:
            return new $class($params[0]);
        case 2:
            return new $class($params[0], $params[1]);
        case 3:
            return new $class($params[0], $params[1], $params[2]);
        case 4:
            return new $class($params[0], $params[1], $params[2], $params[3]);
        case 5:
            return new $class($params[0], $params[1], $params[2], $params[3], $params[4]);
        default:
            try {
                $refClass = new ReflectionClass($class);
                return $refClass->newInstanceArgs($params);
            } catch (ReflectionException $e) {
                throw new Exception("Cannot instantiate {$class}", 0, $e);
            }
    }
}

$this->router()->map($pattern, $callback, $pass_route)操作的目的就是将用户定义的一个或多个route压入到Router对象的$routes属性索引数组中。至此,index.php中的Flight::route()操作就结束了,整个操作流程目的就是获取并解析用户定义的所有route,存储到Router对象的$routes属性索引数组中。接下来的Flight::start(),顾名思义,就是拿着处理好的路由请求信息去真正干活了。

flight/net/Router.php

 /**
 * Maps a URL pattern to a callback function.
 *
 * @param string $pattern URL pattern to match
 * @param callback $callback Callback function
 * @param boolean $pass_route Pass the matching route object to the callback
 */
public function map($pattern, $callback, $pass_route = false) {
    $url = $pattern;
    $methods = array("*");
    //通过用户route定义的匹配规则,解析定义的methods,如"GET|POST /" 
    if (strpos($pattern, " ") !== false) {
        list($method, $url) = explode(" ", trim($pattern), 2);
       
        $methods = explode("|", $method);
    }
    
    $this->routes[] = new Route($url, $callback, $methods, $pass_route);
}

Flight::start()要做的工作就是通过Request对象中获取的真实请求信息与用户所定义的路由进行匹配验证,匹配通过的然后通过Response对象返回给用户请求的结果。

根据刚才的分析,start()方法也会去调用Dispatcher类的invokeMethod方法,但$params是null,所以会执行$class->$method(),通过刚才的分析,会调用Engine对象__call()魔术方法的$this->dispatcher->run($name, $params)。在dispatcher对象的run()方法中,由于start()方法在框架初始化时设置有前置操作,所以在这里会执行所设置的前置操作,最后会执行Engine对象的_start()方法。

这里重点要分析的是从$route = $router->route($request)开始的操作。在实例化Request类获取$request对象时,会做些初始化操作,会将实际的请求信息设置在属性中,用于和用户定义的route进行匹配。

 /**
 * Starts the framework.
 * @throws Exception
 */
public function _start() {
    $dispatched = false;
    $self = $this;
    $request = $this->request(); //获取Request对象
    $response = $this->response(); //获取Response对象
    $router = $this->router(); //获取Router对象

    // Allow filters to run 设置start()方法执行的后置操作
    $this->after("start", function() use ($self) {
        $self->stop();
    });
    
    // Flush any existing output
    if (ob_get_length() > 0) {
        $response->write(ob_get_clean());
    }

    // Enable output buffering
    ob_start();
    
    // Route the request
    while ($route = $router->route($request)) {
        $params = array_values($route->params);

        // Add route info to the parameter list
        if ($route->pass) {
            $params[] = $route;
        }
        
        // Call route handler
        $continue = $this->dispatcher->execute(
            $route->callback,
            $params
        );
        
        $dispatched = true;

        if (!$continue) break;

        $router->next();

        $dispatched = false;
    }

    if (!$dispatched) {
        $this->notFound();
    }
}

flight/net/Request.php

 /**
 * Constructor.
 *
 * @param array $config Request configuration
 */
public function __construct($config = array()) {
    // Default properties
    if (empty($config)) {
        $config = array(
            "url" => str_replace("@", "%40", self::getVar("REQUEST_URI", "/")),
            "base" => str_replace(array(""," "), array("/","%20"), dirname(self::getVar("SCRIPT_NAME"))),
            "method" => self::getMethod(),
            "referrer" => self::getVar("HTTP_REFERER"),
            "ip" => self::getVar("REMOTE_ADDR"),
            "ajax" => self::getVar("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest",
            "scheme" => self::getVar("SERVER_PROTOCOL", "HTTP/1.1"),
            "user_agent" => self::getVar("HTTP_USER_AGENT"),
            "type" => self::getVar("CONTENT_TYPE"),
            "length" => self::getVar("CONTENT_LENGTH", 0),
            "query" => new Collection($_GET),
            "data" => new Collection($_POST),
            "cookies" => new Collection($_COOKIE),
            "files" => new Collection($_FILES),
            "secure" => self::getVar("HTTPS", "off") != "off",
            "accept" => self::getVar("HTTP_ACCEPT"),
            "proxy_ip" => self::getProxyIpAddress()
        );
    }

    $this->init($config);
}

现在来看$router->route($request) 操作都做了什么。$route = $this->current()可以获取到刚才$this->router->map()保存的用户定义的第一个route,如果为false,就会直接返回404。否则,通过$route->matchMethod($request->method) && $route->matchUrl($request->url, $this->case_sensitive)来匹配验证用户定义的routes和实际请求的信息(请求方法和请求url)。

flight/net/Router.php

 /**
 * Routes the current request.
 *
 * @param Request $request Request object
 * @return Route|bool Matching route or false if no match
 */
public function route(Request $request) {
    while ($route = $this->current()) {
        if ($route !== false && $route->matchMethod($request->method) && $route->matchUrl($request->url, $this->case_sensitive)) {
            return $route;
        }
        $this->next();
    }

    return false;
}

flight/net/Route.php

 /**
 * Checks if a URL matches the route pattern. Also parses named parameters in the URL.
 *
 * @param string $url Requested URL
 * @param boolean $case_sensitive Case sensitive matching
 * @return boolean Match status
 */
public function matchUrl($url, $case_sensitive = false) {
    // Wildcard or exact match
    if ($this->pattern === "*" || $this->pattern === $url) {
        return true;
    }

    $ids = array();
    $last_char = substr($this->pattern, -1);
    
    // Get splat
    if ($last_char === "*") {
        $n = 0;
        $len = strlen($url);
        $count = substr_count($this->pattern, "/");
        
        for ($i = 0; $i < $len; $i++) {
            if ($url[$i] == "/") $n++;
            if ($n == $count) break;
        }

        $this->splat = (string)substr($url, $i+1); // /blog/* *匹配的部分
        
    }

    // Build the regex for matching
    $regex = str_replace(array(")","/*"), array(")?","(/?|/.*?)"), $this->pattern);
    
    //对路由匹配实现正则匹配 "/@name/@id:[0-9]{3}"
    $regex = preg_replace_callback(
        "#@([w]+)(:([^/()]*))?#",
        function($matches) use (&$ids) {
            $ids[$matches[1]] = null;
            if (isset($matches[3])) {
                return "(?P<".$matches[1].">".$matches[3].")";
            }
            return "(?P<".$matches[1].">[^/?]+)";
        },
        $regex
    );
    
    // Fix trailing slash
    if ($last_char === "/") {
        $regex .= "?";
    }
    // Allow trailing slash
    else {
        $regex .= "/?";
    }
    
    // Attempt to match route and named parameters
    if (preg_match("#^".$regex."(?:?.*)?$#".(($case_sensitive) ? "" : "i"), $url, $matches)) {
        foreach ($ids as $k => $v) {
            $this->params[$k] = (array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null;
        }

        $this->regex = $regex;

        return true;
    }

    return false;
}

/**
 * Checks if an HTTP method matches the route methods.
 *
 * @param string $method HTTP method
 * @return bool Match status
 */
public function matchMethod($method) {
    return count(array_intersect(array($method, "*"), $this->methods)) > 0;
}



php微框架 flight源码阅读系列

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/29781.html

相关文章

  • php框架 flight源码阅读

    摘要:是一个可扩展的微框架,快速简单,能够快速轻松地构建应用程序,在上有。框架代码十分精简,在几分钟内你就可以看完整个框架源码,使用起来也是很简单优雅。目录微框架源码阅读自动加载微框架源码阅读框架初始化微框架源码阅读路由实现及执行过程 Flight https://github.com/mikecao/fl...是一个可扩展的PHP微框架,快速、简单,能够快速轻松地构建RESTful web...

    CntChen 评论0 收藏0
  • php框架 flight源码阅读——2.框架初始化、Loader、Dispatcher

    摘要:当调用时,会触发当前类的魔术方法,通过判断属性中索引是否存在,不存在抛出异常,存在就通过去实例化初始化时设置的,这里是工厂模式,接下来的路由文章会详细分析。在操作中,会将前置操作设置到类的属性中。微框架源码阅读系列 在自动加载实现完成后,接着new flightEngine()自动加载的方式实例化了下框架的核心类Engine,这个类名翻译过来就是引擎发动机的意思,是flight的引擎发...

    U2FsdGVkX1x 评论0 收藏0
  • php框架 flight源码阅读——1.自动加载

    摘要:先来看下框架的单入口文件,先引入了框架类文件。中定义了加载存放哪些类型类路径数组对象数组框架目录路径数组中使用将当前类中的方法注册为加载的执行方法。接下来我们试着按照自动加载的方式,写个简单的自动加载进行测试微框架源码阅读系列 先来看下框架的单入口文件index.php,先引入了Flight.php框架类文件。

    OnlyLing 评论0 收藏0
  • 从0开始构建一个属于你自己的PHP框架

    摘要:如何构建一个自己的框架为什么我们要去构建一个自己的框架可能绝大多数的人都会说市面上已经那么多的框架了,还造什么轮子。 showImg(https://segmentfault.com/img/bVNg9F?w=500&h=500); 如何构建一个自己的PHP框架 为什么我们要去构建一个自己的PHP框架?可能绝大多数的人都会说市面上已经那么多的框架了,还造什么轮子?。我的观点造轮子不是目...

    vpants 评论0 收藏0
  • 前端每周清单半年盘点之 React 与 ReactNative 篇

    摘要:前端每周清单半年盘点之与篇前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。与求同存异近日,宣布将的构建工具由迁移到,引发了很多开发者的讨论。 前端每周清单半年盘点之 React 与 ReactNative 篇 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为...

    Barry_Ng 评论0 收藏0

发表评论

0条评论

王晗

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<