摘要:當然在對象中也沒有方法,于是會觸發當前對象中的魔術方法。獲取對象獲取對象獲取對象設置方法執行的后置操作現在來看操作都做了什么。匹配的部分對路由匹配實現正則匹配微框架源碼閱讀系列
現在來分析路由實現及執行過程,在項目目錄下創建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()??偨Y:$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源碼閱讀系列
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/29781.html
摘要:是一個可擴展的微框架,快速簡單,能夠快速輕松地構建應用程序,在上有。框架代碼十分精簡,在幾分鐘內你就可以看完整個框架源碼,使用起來也是很簡單優雅。目錄微框架源碼閱讀自動加載微框架源碼閱讀框架初始化微框架源碼閱讀路由實現及執行過程 Flight https://github.com/mikecao/fl...是一個可擴展的PHP微框架,快速、簡單,能夠快速輕松地構建RESTful web...
摘要:當調用時,會觸發當前類的魔術方法,通過判斷屬性中索引是否存在,不存在拋出異常,存在就通過去實例化初始化時設置的,這里是工廠模式,接下來的路由文章會詳細分析。在操作中,會將前置操作設置到類的屬性中。微框架源碼閱讀系列 在自動加載實現完成后,接著new flightEngine()自動加載的方式實例化了下框架的核心類Engine,這個類名翻譯過來就是引擎發動機的意思,是flight的引擎發...
摘要:先來看下框架的單入口文件,先引入了框架類文件。中定義了加載存放哪些類型類路徑數組對象數組框架目錄路徑數組中使用將當前類中的方法注冊為加載的執行方法。接下來我們試著按照自動加載的方式,寫個簡單的自動加載進行測試微框架源碼閱讀系列 先來看下框架的單入口文件index.php,先引入了Flight.php框架類文件。
摘要:如何構建一個自己的框架為什么我們要去構建一個自己的框架可能絕大多數的人都會說市面上已經那么多的框架了,還造什么輪子。 showImg(https://segmentfault.com/img/bVNg9F?w=500&h=500); 如何構建一個自己的PHP框架 為什么我們要去構建一個自己的PHP框架?可能絕大多數的人都會說市面上已經那么多的框架了,還造什么輪子?。我的觀點造輪子不是目...
摘要:前端每周清單半年盤點之與篇前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。與求同存異近日,宣布將的構建工具由遷移到,引發了很多開發者的討論。 前端每周清單半年盤點之 React 與 ReactNative 篇 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為...
閱讀 2018·2021-09-29 09:35
閱讀 1952·2019-08-30 14:15
閱讀 2977·2019-08-30 10:56
閱讀 960·2019-08-29 16:59
閱讀 575·2019-08-29 14:04
閱讀 1306·2019-08-29 12:30
閱讀 1030·2019-08-28 18:19
閱讀 514·2019-08-26 11:51