標籤:flight framework php design
Flight架構(官網)是一個微型的PHP架構,它簡單,快速,可擴充。藉助Flight可以快捷而輕鬆的建立你的RESTFul web應用。
雖然是一個微型的架構,而且代碼量確實也較少,但我在閱讀Flight代碼的過程中,感到了它設計和構思獨特而精妙的地方,覺得有學習的價值,便決定做一下整理分享出來。
如果你對架構還不熟悉,可以先去官網看下文檔,如果需要中文文檔的話可以可以點這裡。
如果你已經對Flight有一定瞭解了,接下來就來看看Flight是怎麼工作的吧。
<?phpclass Flight { /** * Framework engine. * @var object */ private static $engine; // Don't allow object instantiation private function __construct() {} private function __destruct() {} private function __clone() {} /** * 之前已經看到了,架構內所有函數都是以Flight類的靜態函數形式調用的 * __callStatic()這個魔術方法能處理所有的靜態函數 * @param string $name Method name * @param array $params Method parameters * @return mixed Callback results */ public static function __callStatic($name, $params) { static $initialized = false; if (!$initialized) { //這裡定義架構的自動載入機制,實際上是依據PSR-0標準來做的 require_once __DIR__.'/autoload.php'; //Engine類是架構的引擎所在 self::$engine = new \flight\Engine(); $initialized = true; } //在這裡,Flight對Engine封裝了一層而已。對Flight類靜態函數的調用,實質上是對Engine類的相應函數的調用 return \flight\core\Dispatcher::invokeMethod(array(self::$engine, $name), $params); }}//那麼就直接就來看看Dispatcher::invokeMethod函數吧namespace flight\core;class Dispatcher { /** * 調用一個方法 * @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); } }}
上面注釋裡提到了,自動載入和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核心的設計,功能以及處理流程有所認識了吧。至於其他的路由,請求已經響應等內容,就留給讀者自行學習吧。
–EOF–
理解Flight架構核心