理解Flight架構核心

來源:互聯網
上載者:User

標籤: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架構核心

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.