看到了這篇分析flight的文章還不錯,就轉過來了,地址:https://blog.csdn.net/sky_zhe/article/details/38906689
?
Flight框架(官網)是一個微型的PHP框架,它簡單,快速,可擴展。借助Flight可以快捷而輕松的創建你的RESTFul web應用。
雖然是一個微型的框架,而且代碼量確實也較少,但我在閱讀Flight代碼的過程中,感到了它設計和構思獨特而精妙的地方,覺得有學習的價值,便決定做一下整理分享出來。
如果你對框架還不熟悉,可以先去官網看下文檔,如果需要中文文檔的話可以可以點這里。
如果你已經對Flight有一定了解了,接下來就來看看Flight是怎么工作的吧。
上面注釋里提到了,自動加載和PSR-0,我之前寫過一篇關于這部分內容的文章。Flight框架的自動加載就是基于namespace和psr-0標準的:
?
//只列出有關自動加載部分的主要代碼 namespace flight\core; class Loader { /** * Starts/stops autoloader. * * @param bool $enabled Enable/disable autoloading * @param mixed $dirs Autoload directories */ public static function autoload($enabled = true, $dirs = array()) { if ($enabled) { spl_autoload_register(array(__CLASS__, 'loadClass')); } else { spl_autoload_unregister(array(__CLASS__, 'loadClass')); } if (!empty($dirs)) { self::addDirectory($dirs); } } /** * Autoloads classes. * * @param string $class Class name */ public static function loadClass($class) { $class_file = str_replace(array('\\', '_'), '/', $class).'.php'; foreach (self::$dirs as $dir) { $file = $dir.'/'.$class_file; if (file_exists($file)) { require $file; return; } } } }
?
?
再繼續往下看之前,我們不妨先對Flight內主要的類和函數進行一下梳理,以下是Flight框架的內置類:
?
- Engine類:包含了這個框架的核心功能。它的責任是加載HTTP請求,運行已注冊的服務,并生成最后的HTTP響應。
- Loader類:它負責框架內對象的加載。用自定義的初始化參數來生成新的類實例,并且維護可復用的類實例的列表。它還處理剛才提到過的類的自動加載。
- Dispatcher類:它負責框架內事件的分發處理。事件即是對類方法或函數的簡單的稱呼(別名)。它還允許你在事件上的掛鉤點掛載別的函數,能夠改變函數的輸入或者輸出。
- Router類:它負責將一個HTTP講求發送到指定的函數進行處理。它視圖將請求的URL和一系列用戶定義的URL范式進行匹配。
- Route類:它負責路由的具體實現。Router相當于對Route的包裝。
- Request類:它代表了一個HTTP請求。所有來自$_GET,$_POST,$_COOKIE,$_FILES中的數據都要通過Request類獲取和訪問。默認的Request屬性就包括url,base,method,user_agent等。
- Response類:對應于Request,它代表了一個HTTP響應。這個對象包括了返回頭,HTTP狀態碼和返回體。
- View類:視圖類負責將輸出展示。它提供了在渲染時管理視圖數據和將數據插入視圖模板的函數。
- Collection類:它允許你既可以以使用數組的方式,也能以使用對象的方式來訪問數據。
?
Flight框架的函數分兩部分,一部分是核心函數:
?
Flight::map($name, $callback) // Creates a custom framework method. Flight::register($name, $class, [$params], [$callback]) // Registers a class to a framework method. Flight::before($name, $callback) // Adds a filter before a framework method. Flight::after($name, $callback) // Adds a filter after a framework method. Flight::path($path) // Adds a path for autoloading classes. Flight::get($key) // Gets a variable. Flight::set($key, $value) // Sets a variable. Flight::has($key) // Checks if a variable is set. Flight::clear([$key]) // Clears a variable.
?
另一部分是擴展函數:
?
Flight::start() // Starts the framework. Flight::stop() // Stops the framework and sends a response. Flight::halt([$code], [$message]) // Stop the framework with an optional status code and message. Flight::route($pattern, $callback) // Maps a URL pattern to a callback. Flight::redirect($url, [$code]) // Redirects to another URL. Flight::render($file, [$data], [$key]) // Renders a template file. Flight::error($exception) // Sends an HTTP 500 response. Flight::notFound() // Sends an HTTP 404 response. Flight::etag($id, [$type]) // Performs ETag HTTP caching. Flight::lastModified($time) // Performs last modified HTTP caching. Flight::json($data, [$code], [$encode]) // Sends a JSON response. Flight::jsonp($data, [$param], [$code], [$encode]) // Sends a JSONP response.
?
?
Flight框架的使用方式就是對Flight類靜態函數調用(Flight::func()),我們在上面提到過,其實質是對Engine對象中函數的調用($engineObj->func())。
?
而Engine類的函數有兩類,一類是核心函數,是直接進行調用(相對于動態調用)的,另外的擴展函數,則是進行動態調用的。
?
此外,在Flight中加載類和資源,獲得某個類的實例,直接調用Flight::className()即可,等同于$engineObj->className()。這個也是采用動態調用的形式。也就是說,除了Engine類的核心函數,其他函數(類)都是動態調用的。這樣,框架就可以為此提供一個統一的入口了。
?
namespace flight; class Engine { //.... public function __construct() { $this->vars = array(); //上面提到過,Flight中,Dispatcher負責處理函數,Loader負責對象的加載 $this->loader = new Loader(); $this->dispatcher = new Dispatcher(); $this->init(); } /** * __call是一個魔術方法,當調用一個不存在的函數時,會調用到該函數 * 剛才講的動態調用就是通過這個函數進行的 * @param string $name Method name * @param array $params Method parameters * @return mixed Callback results */ public function __call($name, $params) { //先判斷是類還是可直接調用的函數 $callback = $this->dispatcher->get($name); //如果是函數,通過dispatcher處理 if (is_callable($callback)) { return $this->dispatcher->run($name, $params); } //是否是共享實例 $shared = (!empty($params)) ? (bool)$params[0] : true; //通過loader加載該類的對象 return $this->loader->load($name, $shared); } /** * 框架初始化 */ public function init() { static $initialized = false; $self = $this; if ($initialized) { $this->vars = array(); $this->loader->reset(); $this->dispatcher->reset(); } // Flight中,類會通過loader的register函數進行注冊 // 注冊默認組件 $this->loader->register('request', '\flight\net\Request'); $this->loader->register('response', '\flight\net\Response'); $this->loader->register('router', '\flight\net\Router'); $this->loader->register('view', '\flight\template\View', array(), function($view) use ($self) { $view->path = $self->get('flight.views.path'); }); // 注冊框架方法 $methods = array( 'start','stop','route','halt','error','notFound', 'render','redirect','etag','lastModified','json','jsonp' ); // Flight中,method會通過dispatcher的set函數將對應的回調函數綁定到一個事件中 // 為了可以進行動態調用,Enginge的擴展函數全部是通過 _method 的名字定義的 foreach ($methods as $name) { $this->dispatcher->set($name, array($this, '_'.$name)); } // 默認的配置 $this->set('flight.base_url', null); $this->set('flight.handle_errors', true); $this->set('flight.log_errors', false); $this->set('flight.views.path', './views'); $initialized = true; } /** * 將一個類注冊到框架方法中,我們就是通過這個函數注冊我們自定義的類的 * * @param string $name Method name * @param string $class Class name * @param array $params Class initialization parameters * @param callback $callback Function to call after object instantiation * @throws \Exception If trying to map over a framework method */ public function register($name, $class, array $params = array(), $callback = null) { if (method_exists($this, $name)) { throw new \Exception('Cannot override an existing framework method.'); } //通過loader的register函數進行注冊 $this->loader->register($name, $class, $params, $callback); } /** * 將一個回調函數映射到框架方式中,我們就是通過這個函數映射我們自定義函數的 * * @param string $name Method name * @param callback $callback Callback function * @throws \Exception If trying to map over a framework method */ public function map($name, $callback) { if (method_exists($this, $name)) { throw new \Exception('Cannot override an existing framework method.'); } //會通過dispatcher的set函數將對應的回調函數綁定到一個事件中 $this->dispatcher->set($name, $callback); } //... }
?
Flight中的兩個核心函數,map是映射自定義的函數,最后是通過dispathcer的set函數實現的,register是注冊自定義的類,最后是通過loader的register函數實現的。而框架自己的核心組件和擴展函數,Engine在初始化過程幫我們完成了這兩個過程。接著Flight提供了一個統一的入口,可以動態調用所有非核心的函數,類。這就是Flight的核心加載機制了。
?
可能你還有疑問,為什么Flight要使用動態調用的形式去訪問這些函數或對象?尤其是對于Engine的擴展函數,為什么不直接進行調用呢?因為Flight可以對它們進行過濾或重寫。過濾和重寫是Flight框架進行擴展的重要功能。框架實現了統一的資源操作方式后,就可以方便的進行重寫或者過濾的處理了。需要注意的是,核心函數諸如map和register是不能夠進行過濾或重寫的,相信你已經清楚為什么了。
?
框架的重寫功能還是使用的map和register這兩個函數。這個功能因為框架的設計方式,很輕易的完成了。在Dispatcher和Loader中都動態維護了一個映射表,Dispatcher里是回調到事件的映射,Loader中是class到實例和構造函數等的映射。這樣,注冊自定義函數或類時,遇到一樣名字就會覆蓋掉之前的,而使用時只返回最新的。下面是Loader類的部分代碼:
?
namespace flight\core; class Loader { //.... /** * 注冊一個類 * * @param string $name Registry name * @param string|callable $class Class name or function to instantiate class * @param array $params Class initialization parameters * @param callback $callback Function to call after object instantiation */ public function register($name, $class, array $params = array(), $callback = null) { unset($this->instances[$name]); $this->classes[$name] = array($class, $params, $callback); } /** * 加載一個已注冊的類 * * @param string $name Method name * @param bool $shared Shared instance * @return object Class instance */ public function load($name, $shared = true) { $obj = null; //$this->classes是注冊過的類 //$this->instances是加載過的實例 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; } /** * 得到一個類的單一實例 * * @param string $name Instance name * @return object Class instance */ public function getInstance($name) { return isset($this->instances[$name]) ? $this->instances[$name] : null; } /** * 得到一個類的新的實例 * * @param string|callable $class Class name or callback function to instantiate class * @param array $params Class initialization parameters * @return object Class instance */ 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: $refClass = new \ReflectionClass($class); return $refClass->newInstanceArgs($params); } } //.... }
?
跟過濾器功能有關的函數是before和after,分別是在被過濾函數處理之前或之后進行操作。最終是在Dispatcher類中實現的。
?
?
namespace flight; class Engine { /** * Adds a pre-filter to a method. * * @param string $name Method name * @param callback $callback Callback function */ public function before($name, $callback) { $this->dispatcher->hook($name, 'before', $callback); } /** * Adds a post-filter to a method. * * @param string $name Method name * @param callback $callback Callback function */ public function after($name, $callback) { $this->dispatcher->hook($name, 'after', $callback); } } namespace flight\core; class Dispatcher { /** * 將回調注冊到一個事件之中 * * @param string $name Event name * @param callback $callback Callback function */ public function set($name, $callback) { $this->events[$name] = $callback; } /** * 得到事件關聯的回調 * * @param string $name Event name * @return callback $callback Callback function */ public function get($name) { return isset($this->events[$name]) ? $this->events[$name] : null; } /** * 在事件上掛一個回調函數 * * @param string $name Event name * @param string $type Filter type * @param callback $callback Callback function */ public function hook($name, $type, $callback) { $this->filters[$name][$type][] = $callback; } /** * 對事件進行分發處理 * * @param string $name Event name * @param array $params Callback parameters * @return string Output of callback */ public function run($name, array $params = array()) { $output = ''; // 運行前置過濾器 if (!empty($this->filters[$name]['before'])) { $this->filter($this->filters[$name]['before'], $params, $output); } // 運行所請求的方法 $output = $this->execute($this->get($name), $params); // 運行后置過濾器 if (!empty($this->filters[$name]['after'])) { $this->filter($this->filters[$name]['after'], $params, $output); } return $output; } }
?
下面,還差最后一步,運行這個框架時處理流程是怎樣的呢?
?
namespace flight; class Engine { /** * 啟動這個框架 */ public function _start() { $dispatched = false; $self = $this; $request = $this->request(); $response = $this->response(); $router = $this->router(); // 沖刷掉已經存在的輸出 if (ob_get_length() > 0) { $response->write(ob_get_clean()); } // 啟動輸出緩沖 ob_start(); // 開啟錯誤處理 $this->handleErrors($this->get('flight.handle_errors')); // 對AJAX請求關閉緩存 if ($request->ajax) { $response->cache(false); } // 允許后置過濾器的運行 $this->after('start', function() use ($self) { //start完成之后會調用stop()函數 $self->stop(); }); // 對該請求進行路由 while ($route = $router->route($request)) { $params = array_values($route->params); //是否讓路由鏈繼續下去 $continue = $this->dispatcher->execute( $route->callback, $params ); $dispatched = true; if (!$continue) break; $router->next(); } //路由沒找匹配到 if (!$dispatched) { $this->notFound(); } } /** * 停止這個框架并且輸出當前的響應內容 * * @param int $code HTTP status code */ public function _stop($code = 200) { $this->response() ->status($code) ->write(ob_get_clean()) ->send(); } }
至此,應該對Flight核心的設計,功能以及處理流程有所認識了吧。至于其他的路由,請求已經響應等內容,就留給讀者自行學習吧。